Map enum value to a type in C++
Asked Answered
B

4

20

Is there any way to map enum values to types in C++, including C++11.
I have the following enum type:

enum ATTRIBUTE{AGE=0, MENOPAUSE, TUMOR_SIZE, INV_NODES, NODE_CAPS,
               DEG_MALIG, BREAST, BREAST_QUAD, IRRADIAT, CLASS};

I want to map each value of this enum to a certain type. I want to map AGE to int, MENOPAUSE to another enum type, BREAST to bool and so on.
So is it possible to create a function which returns a value of type which depends on the value of the attr variable?

//Like that:
auto value = map_attr(ATTRIBUTE attr);
//Here the type of the value variable should be int if the attr variable is AGE, bool for BREAST and so on.
Bounty answered 1/1, 2017 at 11:57 Comment(6)
I sense an XY Problem. What are you trying to solve with this?Inward
Try boost::mpl::mapOubliette
This answer does it in the other direction: #4995878Oubliette
Who goes from the age zero, worry about the menopause and have a tumor - finally worried about their breast? How are these connected?Likewise
@PaulR I don't think this is a duplicate of that question. This is more a dynamic mapping of enum value (int) to values.Bigamist
@PaulR Damnit, sorry. I wanted to start a discussion and vote to reopen. I'm not used yet to the the fact that I can directly reopen a question. Anyway, feel free to close it again, but I'm still convinced that the questions are slightly different.Bigamist
B
31

An idiomatic way to do it is using traits:

enum ATTRIBUTE{ AGE=0, MENOPAUSE, TUMOR_SIZE, INV_NODES, NODE_CAPS, DEG_MALIG, BREAST, BREAST_QUAD, IRRADIAT, CLASS };

template<ATTRIBUTE> struct Map;

template<> struct Map<AGE> {
    using type = int;
    static constexpr type value = 42;
};

template<> struct Map<MENOPAUSE> {
    using type = AnotherEnumType;
    static constexpr type value = AnotherEnumType::AnotherEnumValue;
};

// ...

Then you can define map_attr as a function template:

template<ATTRIBUTE A>
typename Map<A>::type map_attr() { return Map<A>::value; }

And use it as:

auto something = map_attr<AGE>();

It follows a minimal, working example:

#include<type_traits>

enum ATTRIBUTE{ AGE=0, MENOPAUSE };

template<ATTRIBUTE> struct Map;

template<> struct Map<AGE> {
    using type = int;
    static constexpr type value = 42;
};

template<> struct Map<MENOPAUSE> {
    using type = double;
    static constexpr type value = 0.;
};

template<ATTRIBUTE A>
typename Map<A>::type map_attr() { return Map<A>::value; }

int main() {
    static_assert(std::is_same<decltype(map_attr<AGE>()), int>::value, "!");
    static_assert(std::is_same<decltype(map_attr<MENOPAUSE>()), double>::value, "!");
}
Bigamist answered 1/1, 2017 at 12:17 Comment(4)
Is there any way to use map_attr dynamically, i.e. map_attr<getAttribute()>() where getAttribute() returns an ATTRIBUTE?Khan
It would work as long as getAttribute is a constexpr function probably. Yes.Bigamist
I can't 100% wrap my head around what's going on here, even when I look it all up the documentation and even though I've used basic templating before. Can you please explain why and how you're using the using keyword here, and whether you're creating 1 Map or multiple Map's? Also, what are the limitations to this method? Can I e.g. have a vector<void*> and use this Map approach with that? It looks like you're maybe using the value property as a default value setting here? Thanks.Captive
@Captive There are two specialisations of Map that have definitions. Each have a member type (alias) named type, and a static data member (of that type) named value. There is one template Map, which is instantiated for the two values that it has specialisations for. Instantiating the Map template with any other values would result in a linker error, as the primary template is only a declaration. Since C++20 you can have a std::vector<void*> as a template parameter, because it becomes a LiteralType in that standardSpancel
C
2

I tried out the approach in skypjack's answer using a vector<void*>, to gain a better understanding of it:

#include <iostream>
#include <vector>

using namespace std;

enum Types
{
    Int,
    Double
};

//Use struct instead of class for auto-public.
template<Types> struct TypesMap;

template<> struct TypesMap<Int>
{
    using type = int;
};

template<> struct TypesMap<Double>
{
    using type = double;
};

int main() {
    std::vector<void*> Test;
    
    //Note: Memory leak here, but just as an example.
    Test.push_back(new TypesMap<Int>::type(0.5));
    Test.push_back(new TypesMap<Double>::type(0.5));
    
    std::cout << *((TypesMap<Int>::type*)Test[0]) << " " << *((TypesMap<Double>::type*)Test[1]);
    
    return 0;
}

Output: 0 0.5

Try it here: https://ideone.com/0bN1IP

The problem I've discovered with it, however, is that you cannot put runtime-only enum values (e.g. if you had a vector<Types>) in the <> when you go to use it. This severely limits its capabilities.

My understanding is that this limitation is due to templating existing only for compile-time resolution.


The inherent problem here is wanting dynamic typing whilest c++ is statically typed. So, unless you implement some kind of big switch statement/function to handle each type individually, a polymorphic class, or some other manual type conversion code, it is not possible to have dynamic typing like this resolved at runtime, only that which can be resolved at compile-time. (These other solutions mentioned may be suitable for you if you're okay with somewhat slower code.)

Captive answered 1/2, 2021 at 6:55 Comment(0)
S
0

I wrote a header-only lib called magic_params lib to solve exactly this problem, check it out here.

Basically you define your mapping from enum to type and default value once and can call it's default value at compile time and set and get different values for every enum at runtime.

Example: given an enum like

enum class Param
{
   one = 0,
   two,
   three
};

you can define your mapping with

static constexpr auto settings = create(
       Add<Param::one, int>(99,Description{"first parameter"}), 
       Add<Param::two, std::string>("Default-String", Description("second parameter")),
       Add<Param::three, bool>(true, Description{"third parameter"}));

and call it like that:

constexpr auto value = MyMagicParams::getDefault<Param::two>();

std::cout << "Parameter 'two' has default value " << value;

auto myParams = MyMagicParams();
auto valueBefore = myParams.get<Param::one>();
myParams.set<Param::one>(3);
auto valueAfter = myParams.get<Param::one>();
Stinger answered 14/9, 2022 at 10:57 Comment(0)
W
0

We can use std::conditional(since C++11) to judge type at compile time:

#include <iostream>

enum Types {
    Int, Double
};

template<Types T> struct TypesMap {
    using type = typename std::conditional_t<T == Int, int, double>;
};

template<Types T> using TypesMap_t = typename TypesMap<T>::type;

int main() {
    std::cout << typeid(TypesMap_t<Int>).name() << std::endl;
    std::cout << typeid(TypesMap_t<Double>).name() << std::endl;
    return 0;
}

However std::conditional can only serve two types, but we can nest it:

#include <iostream>

enum Types {
    Int, Double, String
};

template<Types T>
struct TypesMap {
    using type = typename std::conditional_t<
            T == Int, int, typename std::conditional_t<
                    T == Double, double, std::string>>;
};

template<Types T> using TypesMap_t = typename TypesMap<T>::type;

int main() {
    std::cout << typeid(TypesMap_t<Int>).name() << std::endl;
    std::cout << typeid(TypesMap_t<Double>).name() << std::endl;
    std::cout << typeid(TypesMap_t<String>).name() << std::endl;
    return 0;
}

Woodson answered 21/5 at 14:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.