Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
800 views
in Technique[技术] by (71.8m points)

c++ - 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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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;
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...