Vincent
Vincent

Reputation: 60431

Metafunction to convert a type to an integer and vice-versa

The typeid allows to assign a unique std::type_index to each type at runtime. I would like to do the same, statically using two metafunctions :

// Get a unique integral number associated with the provided type
template <class T>
struct encode_type
{
    using type = T;
    static constexpr std::size_t value = /* Metaprogramming magic */;
};

// Get the type uniquely associated with the provided value
template <std::size_t V>
struct decode_type
{
    static constexpr std::size_t value = V;
    using type = /* Metaprogramming magic */;
};

Is there a way to do that in C++11?

Upvotes: 8

Views: 2206

Answers (3)

TartanLlama
TartanLlama

Reputation: 65640

Here's a possible solution which "works" with GCC 5.2 and Clang 3.7.

I use Filip Roséen's Constexpr Meta-Container with some slight alterations. As T.C. pointed out, this may be made ill-formed in the future, so this solution is totally unreasonable in production code, but it's pretty cool for now. I'm not even sure if this is 100% standards-conformant.

// This is our meta-container
using TypeMap = atch::meta_list<class A>;

// Get a unique integral number associated with the provided type
template <class T>
struct encode_type
{
    using type = T;
    // Push T into the container and store the pre-push size
    //( requires slight change to Filip's code)
    static constexpr std::size_t value = TypeMap::push<T>();
};

// Get the type uniquely associated with the provided value
template <std::size_t V>
struct decode_type
{
    static constexpr std::size_t value = V;
    // Get the type at index V
    // (requires a small helper function addition)
    using type = decltype(TypeMap::at<V>());
};

The alterations I made to the original code:

template<class T, class H = meta_list, std::size_t Size = counter::value()>
static constexpr std::size_t push (
  size_type = push_state<
    typename H::template value<>::template push<T>::result
  > ()
) { return Size; } 

I modified atch::meta_list::push to return the size of the meta-container before the push. I used a template parameter with a default argument to ensure that the size is calculated before the push.

template<size_type Idx, class H = meta_list>
static constexpr auto at () -> typename H::template value<>::template at<Idx>::result;

I added a small decltype helper function in atch::meta_list in order to hide all that dependent name mess.


Some test code:

int main () {
    std::array<int, 4> encoded { 
        encode_type<int>::value,
        encode_type<double>::value,
        encode_type<std::string>::value,
        encode_type<float>::value
    };
  
  std::cout << "Encoding: ";
  for (auto i : encoded) std::cout << i << ", ";
  std::cout << std::endl;
  
  std::array<std::type_index, 4> decoded {
      typeid(decode_type<0>::type),  
      typeid(decode_type<1>::type),
      typeid(decode_type<2>::type),
      typeid(decode_type<3>::type),
  };
  
  std::cout << "Decoding: ";
  for (auto i : decoded) std::cout << i.name() << ", ";
  std::cout << std::endl;
}

Both Clang and GCC spew a bunch of warnings, but they both "work"!

Clang compiles, runs and outputs:

Encoding: 0, 1, 2, 3,

Decoding: i, d, NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE, f,

GCC compiles, runs and outputs:

Encoding: 0, 1, 2, 3,

Decoding: i, d, NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE, f,


Maybe you would be better with a pre-processing step...

Upvotes: 9

eh9
eh9

Reputation: 7430

I'll give a partial solution, since I'm not interested in working out all the messy details. Use a macro that takes an argument containing the entire type definition. Now do three things with that argument (there will need to be helper macros):

  • Use the argument as-is to define the type.
  • Pass the argument stringified to a cryptographic hash function.
    • The output of that function will be an integer of some number of bits. No doubt because is cryptographically secure, that integer will be larger than the built-in integral types. So instead of type_id or size_t or whatnot, use your own index type; it can be a POD.
  • The value of that hash function is the magic value for defining encode_type and decode_type.
    • Because of the limitation on the types in the template argument, you might have to use the address of constexpr storage holding the hash value to define decode_type.

The basic principle here is the same one use in Gödel's proof of his incompleteness theorem, namely, that a string of characters always has two interpretations, first as a member of a formal system and second as a number. (The core of the proof, incidentally, is to create a way of speaking about that number from within the formal system.)

The details of using tools readily available in a C++ tool chain are messy, to be sure. The key element, though, that makes it possible is the stringification operator #. This is analogue of the second interpretation above, that of the definition not as a member of a language, but as "just data".

Upvotes: 0

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275740

There are more than types in C++ than can be counted by size_t.

As proof:

template<size_t N> struct bob {};

there are exactly as many bob<X> types as there are size_t values.

template<class...> struct alice {};

now, alice<bob<1>, bob<2>> is a valid type. So is alice<alice<bob<1>>.

There are many more alice types than there are bob types.

Thus there is no injection from the set of size_t values to the set of types.

Thus any such mapping must be incomplete if bijective. You can only map some subset of types to size_t and back again.

You did not specify you only wanted to handle a subset of types, so the answer is, you cannot do what you asked.

Note that std::type_index is simply a hashable, comparable object. It is not an "index" like an integer value.

Upvotes: 4

Related Questions