19

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.
skypjack
  • 49,335
  • 19
  • 95
  • 187
Petko Six
  • 285
  • 1
  • 3
  • 7
  • 3
    I sense an [XY Problem](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). What are you trying to solve with this? – StoryTeller - Unslander Monica Jan 01 '17 at 12:13
  • 1
    Try `boost::mpl::map` – Nim Jan 01 '17 at 12:23
  • 3
    This answer does it in the other direction: http://stackoverflow.com/questions/4995378/c-template-for-mapping-struct-type-to-enum – Nim Jan 01 '17 at 12:29
  • Who goes from the age zero, worry about the menopause and have a tumor - finally worried about their breast? How are these connected? – Ed Heal Jan 01 '17 at 17:20
  • @PaulR I don't think this is a duplicate of that question. This is more a _dynamic mapping of enum value (int) to values_. – skypjack Jan 01 '17 at 17:52
  • @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. – skypjack Jan 01 '17 at 17:58

3 Answers3

30

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, "!");
}
skypjack
  • 49,335
  • 19
  • 95
  • 187
  • 1
    Is there any way to use `map_attr` dynamically, i.e. `map_attr()` where `getAttribute()` returns an ATTRIBUTE? – maga Jul 01 '17 at 17:34
  • 5
    It would work as long as `getAttribute` is a `constexpr` function probably. Yes. – skypjack Jul 01 '17 at 17:35
  • 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 and use this Map approach with that? It looks like you're maybe using the value property as a default value setting here? Thanks. – Andrew Feb 01 '21 at 06:57
1

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.)

Andrew
  • 5,839
  • 1
  • 51
  • 72
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>();
EddyXorb
  • 68
  • 7