views:

428

answers:

6

I just started playing with metaprogramming and I am working on different tasks just to explore the domain. One of these was to generate a unique integer and map it to type, like below:

int myInt = TypeInt<AClass>::value;

I want to know if this is at all possible, and in that case how. Because although I have learned much about exploring this subject I still have failed to come up with an answer.

(P.S. A yes/no answer is much more gratifying than a c++ solution that doesn't use metaprogramming, as this is the domain that I am exploring)

+3  A: 

In principle, this is possible, although the solution probably isn't what you're looking for.

In short, you need to provide an explicit mapping from the types to the integer values, with one entry for each possible type:

template< typename T >
struct type2int
{
   // enum { result = 0 }; // do this if you want a fallback value
};

template<> struct type2int<AClass> { enum { result = 1 }; };
template<> struct type2int<BClass> { enum { result = 2 }; };
template<> struct type2int<CClass> { enum { result = 3 }; };

const int i = type2int<T>::result;

If you don't supply the fallback implementation in the base template, this will fail for unknown types if T, otherwise it would return the fallback value.

Depending on your context, there might be other possibilities, too. For example, you could define those numbers within within the types themselves:

class AClass {
  public:
    enum { inta_val = 1 };
  // ...
};

class BClass {
  public:
    enum { inta_val = 2 };
  // ...
};

// ... 

template< typename T >
struct type2int
{
   enum { result = T::int_val }; // will fail for types without int_val
};

If you give more context, there might be other solutions, too.

Edit:

Actually there isn't any more context to it. I was looking into if it actually was possible, but without assigning the numbers itself.

I think Mike's idea of ordering is a good way to do this (again, for a fixed set of types) without having to explicitly assign numbers: they're implicitly given by the ordering. However, I think that this would be easier by using a type list. The index of any type in the list would be its number. I think something like the following might do:

// basic type list manipulation stuff
template< typename T1, typename T2, typename T3...>
struct type_list;

// meta function, List is assumed to be some instance of type_list
template< typename T, class List >
struct index_of {
  enum { result = /* find index of T in List */ };
};

// the list of types you support
typedef type_list<AClass, BClass, CClass> the_type_list;

// your meta function
template< typename T >
struct type2int
{
   enum { result = index_of<T, the_type_list>::result };
};
sbi
Actually there isn't any more context to it. I was looking into if it actually was possible, but without assigning the numbers itself.
daramarak
Re the `type_list`: you'll need C++0x or compiler-specific extensions for the variadic template. Apart from that, it's neater than my solution.
Mike Seymour
@Mike: You can do that without variadic templates (type lists have been out there for a long time), although it's not as neat and the maximal number of types is fixed. In fact, I added the `...` merely to suggest more parameters, and it only occurred to me afterwards that it could be read as variadic templates. (I don't even know if this is the right syntax.)
sbi
+2  A: 

The closest I've come so far is being able to keep a list of types while tracking the distance back to the base (giving a unique value). Note the "position" here will be unique to your type if you track things correctly (see the main for the example)

template <class Prev, class This>
class TypeList
{
public:
   enum
   {
      position = (Prev::position) + 1,
   };
};

template <>
class TypeList<void, void>
{
public:
  enum
  {
     position = 0,
  };
};


#include <iostream>

int main()
{
        typedef TypeList< void, void> base;  // base
        typedef TypeList< base, double> t2;  // position is unique id for double
        typedef TypeList< t2, char > t3; // position is unique id for char

        std::cout << "T1 Posn: " << base::position << std::endl;
        std::cout << "T2 Posn: " << t2::position << std::endl;
        std::cout << "T3 Posn: " << t3::position << std::endl;

}

This works, but naturally I'd like to not have to specify a "prev" type somehow. Preferably figuring out a way to track this automatically. Maybe I'll play with it some more to see if it's possible. Definitely an interesting/fun puzzle.

Doug T.
This is the same solution that I thought of, but I never got around to removing that "prev". But guess there is no way to save this state. As I understand this correspond very much to the functional nature of template metaprogramming.
daramarak
+2  A: 

I think it is possible to do it for a fixed set of types, but quite a bit of work. You'll need to define a specialisation for each type, but it should be possible to use compile-time asserts to check for uniqueness. I'll assume a STATIC_ASSERT(const_expr), like the one in Boost.StaticAssert, that causes a compilation failure if the expression is false.

Suppose we have a set of types that we want unique IDs for - just 3 for this example:

class TypeA;
class TypeB;
typedef int TypeC;

We'll want a way to compare types:

template <typename T, typename U> struct SameType
{
    const bool value = false;
};

template <typename T> struct SameType<T,T>
{
    const bool value = true;
};

Now, we define an ordering of all the types we want to enumerate:

template <typename T> struct Ordering {};

template <> struct Ordering<void>
{
    typedef TypeC prev;
    typedef TypeA next;
};

template <> struct Ordering<TypeA>
{
    typedef void  prev;
    typedef TypeB next;
};

template <> struct Ordering<TypeB>
{
    typedef TypeA prev;
    typedef TypeC next;
};

template <> struct Ordering<TypeC>
{
    typedef TypeB prev;
    typedef void  next;
};

Now we can define the unique ID:

template <typename T> struct TypeInt
{
    STATIC_ASSERT(SameType<Ordering<T>::prev::next, T>::value);
    static int value = TypeInt<T>::prev::value + 1;
};

template <> struct TypeInt<void>
{
    static int value = 0;
};

NOTE: I haven't tried compiling any of this. It may need typename adding in a few places, and it may not work at all.

You can't hope to map all possible types to an integer field, because there are an unbounded number of them: pointer types with arbitrary levels of indirection, array types of arbitrary size and rank, function types with arbitrary numbers of arguments, and so on.

Mike Seymour
Stupid question from non template metaprogrammer. How is TypeA/TypeB/TypeC resolved when its not specified as a template parameter or specialization? It seems like somehow these need to bound (either implicitly somehow or explicitly). I don't see how that's being done here or how it would be done. I've tried compiling your code, and I get errors about Type(A|B|C) not being defined, and I'm not sure how to resolve it.
Doug T.
They are the types that you want to enumerate (such as "AClass" in the question). I've added example declarations to the answer.
Mike Seymour
@Mike: I like your idea of ordering. However, don't you think putting the types into a type list and using their indexes in that type list would be much simpler?
sbi
@Mike: I have appended an outline of such a solution to my answer: http://stackoverflow.com/questions/1708458/1708628#1708628
sbi
A: 

I don't think it's possible without assigning the numbers yourself or having a single file know about all the types. And even then you will run into trouble with template classes. Do you have to assign the number for each possible instantiation of the class?

Matt Price
I thought of the problem like this: Only the types that uses TypeToInt<T> should be given a number.
daramarak
A: 

This does what you want. Values are assigned on need. It takes advantage of the way statics in functions are assigned.

inline size_t next_value()
{
     static size_t id = 0;
     size_t result = id;
     ++id;
     return result;
}

/** Returns a small value which identifies the type.
    Multiple calls with the same type return the same value. */
template <typename T>
size_t get_unique_int()
{
     static size_t id = next_value();
     return id;
}

It's not template metaprogramming on steroids but I count that as a good thing (believe me!)

Matthew Herrmann
But this will not generate the the values at compile time, which are the purpose of this task.
daramarak
A: 

This may be doing some "bad things" and probably violates the standard in some subtle ways... but thought I'd share anyway .. maybe some one else can sanitise it into something 100% legal? But it seems to work on my compiler.

The logic is this .. construct a static member function for each type you're interested in and take its address. Then convert that address to an int. The bits that are a bit suspect are : 1) the function ptr to int conversion. and 2) I'm not sure the standard guarantees that the addresses of the static member functions will all correctly merge for uses in different compilation units.

typedef void(*fnptr)(void);

union converter
{
  fnptr f;
  int i;
};

template<typename T>
struct TypeInt
{
  static void dummy() {}
  static int value() { converter c; c.f = dummy; return c.i; }
};

int main()
{
  std::cout<< TypeInt<int>::value() << std::endl;
  std::cout<< TypeInt<unsigned int>::value() << std::endl;
  std::cout<< TypeInt< TypeVoidP<int> >::value() << std::endl;
}
Michael Anderson
Also note that for many uses where you just need to compare the ints you can get away without the conversion from function pointer to int, and just compare the function pointers to the dummy function.
Michael Anderson
But this will not give you an unique typeint at compile time will it?
daramarak
Correct, the int from this is not a compile time constant. However I think that the address of TypeInt<T>::dummy is a compile time constant if that will suffice.
Michael Anderson