Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 270
Peter Lenkefi Avatar asked Feb 15 '17 19:02

Peter Lenkefi


3 Answers

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);
 }
like image 198
max66 Avatar answered Sep 21 '22 23:09

max66


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.
like image 42
PaperBirdMaster Avatar answered Sep 18 '22 23:09

PaperBirdMaster


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

like image 25
SergeyA Avatar answered Sep 17 '22 23:09

SergeyA