surfcode
surfcode

Reputation: 465

Building a list of types during compile time - no C++11

I'd like to do exact this to get a list of types/classes. But I cannot use C++11. Any suggestion how I can append type to the template list?

Edit: some code to what I'd like to do:

#include <iostream>
#include <typeinfo>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/push_back.hpp>
#include <boost/mpl/for_each.hpp>

using namespace std;

class A {};
class B {};
class C {};

typedef boost::mpl::vector<> type1;
// supposed I'd like to have this as a #define macro so someone can call
// REGISTER_CLASS(A) and push the type into a list
typedef boost::mpl::push_back<type1, A> type2;
typedef boost::mpl::push_back<type2, B> type3;
typedef boost::mpl::push_back<type3, C> type4;

template <typename T> struct wrap {};

struct Print
{
      template <typename T> void operator()( wrap<T> t ) const
      {
            cout << typeid( T ).name() << endl;
      }
};

int main()
{
      // this doesn't compile because type4 is a sequence of sequence
      // supposed, I need to "flatten" this list so it's eqv to vector<A,B,C>
      boost::mpl::for_each<type4, wrap<boost::mpl::placeholders::_1> >( Print() );

      // second problem is that I'd like to have typedef of 1 typelist only, not
      // type1, type2, ... typeN, since I don't know the exact number of classes

      return 0;
}

Upvotes: 3

Views: 2235

Answers (1)

First off, the actual problem why your code doesn't compile is that you're typedefing the operations, not their results. Change it like this:

typedef boost::mpl::push_back<type1, A>::type type2;
typedef boost::mpl::push_back<type2, B>::type type3;
typedef boost::mpl::push_back<type3, C>::type type4;

Now the more general view of why you need the typedefs.

Using metaprogramming techniques (like template metaprogramming in your case) requires a mental "shift," because the paradigm is different than "normal" C++. C++ itself is an imperative language. Metaprograms are functional.

As far as data structures are concerned, the functional paradigm means data structures are always immutable. In an imperative language, for example, you do this:

vector<int> v;
v.push(4);
v.push(7);
v.push(42);
process(&v);
print(v);

In a functional language, the equivalent code would be:

v = vector<int>;
v1 = v.push(4);
v2 = v1.push(7);
v3 = v2.push(42);
v4 = process(v3);
print(v4);

Or, using more functional notation, something like this:

print(process(vector<int>().push(4).push(7).push(42)));

In a purely functional (LISP-style) notation, it would be:

(print (process (push (push (push (vector<int>) 4) 7) 42)))

An alternative way to put it is that in functional languages, data structures aren't stored, they are produced.

Coming back to C++ template metaprogramming and Boost.MPL, this means the only way to append type T to a type list (MPL vector) v is to pass boost::mpl::push_back<v, T>::type there where you want to handle the "vector with T pushed back". To maintain DRY, you normally do this with a typedef for the "vector with T pushed back."


Unfortunately, this means there is no way to create a REGISTER_CLASS macro the way you'd like to. You could make something similar with a bit more metaprogramming (including use of Boost.Preprocessor).

The idea is to use a Boost.Preprocessor slot to hold the last identifier of the type list. Instead of calling the REGISTER_CLASS macro directly, it would then be #included, with the class name being passed through another macro. Something like this:

internal_register.hpp

//This file MUST NOT have include guards

#ifndef CLASS_TO_REGISTER
  #error You must define CLASS_TO_REGISTER before executing REGISTER_CLASS()
#endif

typedef boost::mpl::push_back<
  CURRENT_TYPELIST(),
  CLASS_TO_REGISTER
>::type BOOST_PP_CAT(registered, BOOST_PP_INC(BOOST_PP_SLOT(1)));

#undef CLASS_TO_REGISTER

#define BOOST_PP_VALUE BOOST_PP_SLOT(1) + 1
#include BOOST_PP_ASSIGN_SLOT(1)

registration.hpp

typedef boost::mpl::vector<>::type registered0;

#define BOOST_PP_VALUE 0
#include BOOST_PP_ASSIGN_SLOT(1)

#define REGISTER_CLASS() "internal_register.hpp"

#define CURRENT_TYPELIST() BOOST_PP_CAT(registered, BOOST_PP_SLOT(1))

main.cpp (or any other usage):

#include "registration.hpp"

class A {};
class B {};
class C {};

#define CLASS_TO_REGISTER A
#include REGISTER_CLASS()

#define CLASS_TO_REGISTER B
#include REGISTER_CLASS()

#define CLASS_TO_REGISTER C
#include REGISTER_CLASS()

template <typename T> struct wrap {};

struct Print
{
      template <typename T> void operator()( wrap<T> t ) const
      {
            cout << typeid( T ).name() << endl;
      }
};

int main()
{
      boost::mpl::for_each<CURRENT_TYPELIST(), wrap<boost::mpl::placeholders::_1> >( Print() );

      return 0;
}

It would take some more tweaking to work correctly when more than one translation unit is involved (some things can't be done at all in such case, some things can be done if appropriate identifiers are put in an anonymous namespace). But it should serve as a starting point.

Upvotes: 5

Related Questions