Peter Lenkefi
Peter Lenkefi

Reputation: 1346

C++ constexpr values for types

I want to be able to create switch statements over a type's ID. I've found a mechanism that could give a unique ID for different types. It's very simple:

template <typename T>
struct type { 
    static void id() { } 
};

template <typename T>
constexpr const size_t type_id() {
    return reinterpret_cast<size_t>(&type<T>::id); 
}

I thought this would evaluate to a constant that I could use as cases for the switch. But I get an error that the case expression is not a constant when I do the following:

int main(void) {
    size_t a = type_id<int>();
    switch (a) {
    case type_id<int>():
        break;
    }
    return 0;
}

Why is it not a constant? How could I achieve this effect?

Edit:

Can I do something like this without the reinterpret_cast then?

Upvotes: 6

Views: 4762

Answers (4)

PaperBirdMaster
PaperBirdMaster

Reputation: 13298

I would like to suggest another approach which involves constexpr functions and macros (eeeewww...):

// Some naive text hashing function
template <std::size_t SIZE>
constexpr std::size_t hash(const char (&type_name)[SIZE])
{
    std::size_t result{0xf};
    for (const auto &c : type_name)
    {
        result <<= 1;
        result |= c;
    }

    return result;
}

First we create a constexpr function able to transform a string literal into a number, this is my approach but you can choose anoter function as long as it is constexpr, then we create a macro which stringify the given parameter using the #:

#define TYPE_ID(X) hash(#X)

And now, we can use it:

int main(void) {
    size_t a = TYPE_ID(int);
    switch (a) {
    case TYPE_ID(int):
        break;
    }
    return 0;
}

Pros:

  • Pretty straightforward.
  • Tiny amount of code.

Cons:

  • Macros.
  • Accepts any value, included nonsense: TYPE_ID(I LOVE BACON) is valid.
  • Yields different result for TYPE_ID(size_t) and TYPE_ID(unsigned long) even if they might be the same type.

Upvotes: 4

nwp
nwp

Reputation: 10001

This may solve your problem:

#include <tuple>

//Index from http://stackoverflow.com/a/18063608/3484570
template <class T, class Tuple>
struct Index;

template <class T, class... Types>
struct Index<T, std::tuple<T, Types...>> {
    static const std::size_t value = 0;
};

template <class T, class U, class... Types>
struct Index<T, std::tuple<U, Types...>> {
    static const std::size_t value = 1 + Index<T, std::tuple<Types...>>::value;
};

template <class T>
constexpr std::size_t type_id() {
    //need to add every type in this tuple:
    return Index<T, std::tuple<int, double, char>>::value;
}

int main() {
    size_t a = type_id<int>();
    switch (a) {
        case type_id<int>():
            break;
    }
}

The good news is that you get a type_id<T>() that you can use in a constexpr context such as in a case like you wanted.
The bad news is that you need to list all supported types.
In practice you might get used to the error message that occurs when you ask for the type_id of an unsupported type and just add it and eventually add all relevant types.

Upvotes: 3

max66
max66

Reputation: 66230

I'm not sure it's a good idea but... just for fun... using the constexpr counter, suggested in this page, you should be able to substitute the value of the pointer.

The following is a (I repeat: just for fun) full experiment

#include <iostream>

template <int N>
struct flag
 { friend constexpr int adl_flag (flag<N>); };

template <int N>
struct writer
 {
   friend constexpr int adl_flag (flag<N>)
    { return N; }

   static constexpr int value { N };
 };

template <int N, int = adl_flag (flag<N> {})>
int constexpr reader (int, flag<N>)
 { return N; }

template <int N>
int constexpr reader (float, flag<N>, int R = reader (0, flag<N-1> {}))
 { return R; }

int constexpr reader (float, flag<0>)
 { return 0; }

template <int N = 1>
int constexpr next (int R = writer<reader (0, flag<32> {}) + N>::value)
 { return R; }

template <typename T>
struct type
 { 
   static constexpr int id { next() };

   constexpr static int type_id ()
    { return id; }
 };

void printType (int idT )
 {
   switch ( idT )
    {
      case type<int>::type_id():
         std::cout << "- int type" << std::endl;
         break;

      case type<long>::id:
         std::cout << "- long type" << std::endl;
         break;

      default:
         std::cout << "- another type" << std::endl;
         break;
    }
 }

int main ()
 {
   int ii { type<int>::id };
   int il { type<long>::type_id() };

   printType(ii);
   printType(il);
 }

Upvotes: 4

SergeyA
SergeyA

Reputation: 62603

constexpr functions can not use reinterpret_cast in any shape or form. Some more formal reading can be found at http://en.cppreference.com/w/cpp/language/constant_expression

Upvotes: 3

Related Questions