Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++11 type to enum mapping?

I have an enum like:

enum E
{
    TYPE_FLOAT,
    TYPE_CHAR,
    TYPE_INT
}

And I want to create a compile-time mapping to get the appropriate E for a type like:

GetE<float> // returns TYPE_FLOAT
GetE<char> // returns TYPE_CHAR
GetE<int> // returns TYPE_INT

I thought of:

template<class T> struct GetE;

template<> struct GetE<float> { static constexpr E type = TYPE_FLOAT; };
template<> struct GetE<char> { static constexpr E type = TYPE_CHAR; };
template<> struct GetE<int> { static constexpr E type = TYPE_INT; };

But I'm getting errors like:

undefined reference to `GetE<int>::type'

Whats the best way to do this? And why the error?

like image 740
Andrew Tomazos Avatar asked Feb 27 '13 13:02

Andrew Tomazos


3 Answers

It depends on how you use these constant expressions.

The ODR (one-definition rule) states that

(§3.2/2) [...] A variable whose name appears as a potentially-evaluated expression is odr-used unless it is an object that satisfies the requirements for appearing in a constant expression (5.19) and the lvalue-to-rvalue conversion (4.1) is immediately applied. [...]

(And then, lots of special rules, exceptions and exceptions of the exceptions follow.)

Any variable that is odr-used, must have exactly one definition. Your constant expressions have a declaration, but not a definition, so this goes well unless you odr-use one of them.

For example, the following goes well:

int main() {
  E e = GetE<float>::type;
  return 0;
}

But this does not:

void f(const E &)
{ }

int main() {
  f(GetE<float>::type);
  return 0;
}

because f requires a (const) reference, so the lvalue-to-rvalue conversion cannot be applied immediately, hence this constitutes an odr-use. The compiler will complain that it misses a definition.

(Remark. As ShafikYaghmour found (see the comments), you may not get a complaint if the compiler uses optimization, as the references may be optimized away. To reproduce the compiler complaint, use the -O0 flag (or similar, depending on the compiler).)

To solve the problem, the required definition can be provided in the usual way, i.e. outside the struct-definition:

constexpr E GetE<float>::type;
constexpr E GetE<char>::type;
constexpr E GetE<int>::type;

But since this would have to happen in the .cpp (not the header file), you'll end up having to maintain the declarations and definitions in two different places, which is cumbersome.

The solution you've just suggested in your comment, i.e. define a constexpr (and inline) function, sounds right:

template <class T> constexpr E GetE();

template <> constexpr E GetE<float>()
{ return TYPE_FLOAT; }

template <> constexpr E GetE<char>()
{ return TYPE_CHAR; }

template <> constexpr E GetE<int>()
{ return TYPE_INT; }

void f(const E &)
{ }

int main() {
  E e = GetE<float>();

  f(GetE<float>());

  return 0;
}
like image 184
jogojapan Avatar answered Oct 19 '22 22:10

jogojapan


Static member variables need to be defined outside the class scope:

class C {
    const static int x = 5;
};

decltype(C::x) C::x;
like image 42
Bartek Banachewicz Avatar answered Oct 19 '22 22:10

Bartek Banachewicz


Maybe because you forgot to put a semicolon after the enum definition, this works for me in LiveWorkSpace:

#include <iostream>

enum E
{
   TYPE_FLOAT,
   TYPE_CHAR,
   TYPE_INT
} ;

template<class T> struct GetE;

template<> struct GetE<float> { static constexpr E type = TYPE_FLOAT; };
template<> struct GetE<char> { static constexpr E type = TYPE_CHAR; };
template<> struct GetE<int> { static constexpr E type = TYPE_INT; };

int main()
{
    std::cout << GetE<int>::type << std::endl ;
}

here is a link to the code http://liveworkspace.org/code/nHqUe$6

like image 35
Shafik Yaghmour Avatar answered Oct 19 '22 20:10

Shafik Yaghmour