Building and Accessing a List of Types at Compile Time

building and accessing a list of types at compile time

A solution utilizing a common header, variadic templates and a macro:

// Header common.h

// A distinct Void type
struct Void {};

template <typename ...> struct concat;

template <template <typename ...> class List, typename T>
struct concat<List<Void>, T>
{
typedef List<T> type;
};

template <template <typename ...> class List, typename ...Types, typename T>
struct concat<List<Types...>, T>
{
typedef List<Types..., T> type;
};

template <typename...> struct TypeList {};

template <>
struct TypeList<Void> {};
typedef TypeList<Void> TypelistVoid;
#define TYPE_LIST TypelistVoid
// Header foo.h
#include <common.h>

class Foo { };

typedef typename concat<TYPE_LIST, Foo>::type TypeListFoo;
#undef TYPE_LIST
#define TYPE_LIST TypeListFoo
// Header bar.h
#include <common.h>

class Bar { };

typedef typename concat<TYPE_LIST, Bar>::type TypeListBar;
#undef TYPE_LIST
#define TYPE_LIST TypeListBar
// Header main.h 
#include "foo.h"
#include "bar.h"

struct list_of_types {
typedef TYPE_LIST type;
};
// Or just typedef TYPE_LIST list_of_types;

// Test
#include <iostream>
#include <typeinfo>

template <template <typename ...> class List, typename T, typename ...Types>
void info();

template <typename T, typename ...Types>
inline void info(TypeList<T, Types...>) {
std::cout << typeid(T).name() << std::endl;
info(TypeList<Types...>());
}

template <typename T>
inline void info(TypeList<T>) {
std::cout << typeid(T).name() << std::endl;
}

int main() {
info(list_of_types::type());
return 0;
}

Assembling a compile-time list of types one by one (C++)

After consulting with comp.lang.c++.moderated I came up with the code detailed below. The PushBack() macro is used to add new items to the list, one can iterate through the items by using the Next() macro, and the last item can be recognized from that its 'next item' is itself. So a very basic example code:

struct A {}; 

namespace NS1 {
struct B {};
struct C {};
struct D {};

// building up the compile-time list
struct Head;

PushBack(Head, A);
PushBack(Head, B);
}

PushBack(NS1::Head, NS1::C);

namespace NS1 {
PushBack(Head, D);
}

// iterate through the list
namespace NS2 {

// end of the list reached (recognized from PrevItem == Item)
template <class ListId, class Item>
void print(Wrapper<Item>, Wrapper<Item>) {}

// process Item and advance to the next element
template <class ListId, class PrevItem, class Item>
void print(Wrapper<PrevItem>, Wrapper<Item>)
{
cout << typeid(Item).name() << endl;
print<ListId>(Wrapper<Item>(), Wrapper< Next(ListId, Item) >());
}
}

A more detailed example that also contains an iterator to ease the use of the list can be found here or here.

The most important part of the 'library':

/// Helper class to wrap incomplete types and avoid instantiation of T 
template<class T> struct Wrapper {};

namespace CTList {
/// The front of compile-time lists
struct Nil {};
}

/// Compile-time list helper
template< typename ListId, typename Item >
Item NextInListHelper( ::Wrapper<ListId>, ::Wrapper<Item> );

/// The last element of the list
#define Back(ListId) \
decltype( NextInListHelper( ::Wrapper<ListId>(), ::Wrapper<\
decltype( NextInListHelper( ::Wrapper<ListId>(), ::Wrapper<\
decltype( NextInListHelper( ::Wrapper<ListId>(), ::Wrapper<\
decltype( NextInListHelper( ::Wrapper<ListId>(), ::Wrapper<\
decltype( NextInListHelper( ::Wrapper<ListId>(), ::Wrapper<\
decltype( NextInListHelper( ::Wrapper<ListId>(), ::Wrapper<\
decltype( NextInListHelper( ::Wrapper<ListId>(), ::Wrapper<\
decltype( NextInListHelper( ::Wrapper<ListId>(), ::Wrapper<\
decltype( NextInListHelper( ::Wrapper<ListId>(), ::Wrapper<\
decltype( NextInListHelper( ::Wrapper<ListId>(), ::Wrapper<\
CTList::Nil \
>())) \
>())) \
>())) \
>())) \
>())) \
>())) \
>())) \
>())) \
>())) \
>()))

/// Add a new element (type) to the list
#define PushBack( ListId, item) \
item NextInListHelper( ::Wrapper< ListId >, ::Wrapper< Back(ListId) > )

/// The next element in the ('ListId') list after 'item'
#define Next(ListId, item) decltype(NextInListHelper(::Wrapper<ListId>(), ::Wrapper<item>() ))

It uses NextInListHelper function declarations and Argument Dependent (Name)Look-up to record the list. The last element of the list can be referenced using the Back macro.

The first element of the list can be accessed as: Next(ListId, CTList::Nil),
the last element of the list can be recognized from that it is the
same as its next element (LastItem == Next(ListId, LastItem)).

I tested it only with gcc 4.6.3, but it is intended to be fully C++11 compliant.

A few words about the solution:

  • in this form it can handle type lists up to 10 elements, but this can be extended by adding extra lines to the Back() macro
  • the 'list' can contain a type only once
  • the additional ListId type used in NextInListHelper declarations is there to allow types to be contained in multiple lists simultaneously
  • Wrapper is used to avoid actual instantiation of list element types and to support incomplete types as ListId
  • PushBack() 'calls' must be in the namespace of ListId (NS1) or Wrapper (the global scope)
  • The processing of the list can be put in any namespace (NS2)

How to instantiate a list of types for compile-time/static polymorphism

I would suggest changing the typedefs for A, B and C to struct so you can define the string inside them.

struct A {
static constexpr int value = 1;
static constexpr char name[] = "A";
};

// Same for B and C

using list = mp_list<A, B, C>;

Then you can create a make_thing_list

template <typename... T>
static std::array<Things, sizeof...(T)> make_thing_list(mp_list<T...>) {
return {{{&Test::process<T>, T::name}...}};
}

auto thing_list = make_thing_list(list{});

Complete example

#include <string>
#include <array>
#include <iostream>

template <typename... T>
struct mp_list {};

struct Test
{
struct nop {
static constexpr int value = 0;
static constexpr char name[] = "nop";
};
struct A {
static constexpr int value = 1;
static constexpr char name[] = "A";
};
struct B {
static constexpr int value = 2;
static constexpr char name[] = "B";
};
struct C {
static constexpr int value = 3;
static constexpr char name[] = "C";
};

using list = mp_list<A, B, C>; // mp_list expands to: template<A, B, C> struct {};

struct Things{
int (Test::*process)(int foo, float bar);
const std::string key;
int something;
float other;
};

template <typename... T>
static std::array<Things, sizeof...(T)> make_thing_list(mp_list<T...>) {
return {{{&Test::process<T>, T::name}...}};
}

using Thing_list = decltype(make_thing_list(list{}));

Thing_list thing_list = make_thing_list(list{});

template<typename T=nop> int process(int foo, float bar) {
return T::value;
}

// stuff...

Test() {}
};

int main() {
Test t;

static_assert(std::is_same_v<decltype(t.thing_list), std::array<Test::Things, 3>>);

for (auto& thing : t.thing_list) {
std::cout << thing.key << (t.*thing.process)(1, 1.0) << '\n';
}
}

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

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.

Constructing a compile-time list of templated types?

Instead of storing component_id in the vector in entity, you could use type erasure and store elements that know which Component type to deal with.

#include <memory>

struct component_id{};

struct component_type_ref
{
template <typename Comp>
component_type_ref()
: _id(Comp::id), _impl(std::make_shared<_impl_t<Comp>>())
{
}

component_id id() const
{
return _id;
}

void clear()
{
_impl->clear();
}

private:
struct _impl_base
{
virtual void clear();
};

template <typename Comp>
struct _impl_t : public _impl_base
{
void clear()
{
Comp::components.clear();
}
};

component_id _id;
std::shared_ptr<_impl_base> _impl;
};

See also https://channel9.msdn.com/Events/GoingNative/2013/Inheritance-Is-The-Base-Class-of-Evil

This way, for instance, you can lookup a component id in the vector of an entity and just call clear on it. So might be a solution if there is a know list of operations you want to run on the component types (and those operations do not need to be templates)

If that is not the case, then I am afraid you will need to use type vectors. No need to use tuple here, a simple


template<typename... T> struct my_type_vector{};

would be sufficient, probably. But as you wrote yourself, this is not so feasible at runtime.

C++14 Metaprogramming: Automagically build a list of types at compile / init time

To do it at compile-time will require "stateful" metaprogramming. In this article here, Filip Roséen explains how to implement the following using extremely advanced C++14:

LX::push<void, void, void, void> ();
LX::set<0, class Hello> ();
LX::set<2, class World> ();
LX::pop ();

LX::value<> x; // type_list<class Hello, void, class World>

Also, Matt Calabrese used similar techniques to implements semantic-based concepts in C++11, see the video and slides at slide #28.

Of course, these techniques rely on a compiler supporting conformant two-phase name lookup.

Alternatively, you can restucture your code to support runtime registration instead, which is much simpler, and can work portably across compilers such as MSVC. This is what libraries such as Prove or args use. It uses a generic auto_register class:

template<class T, class F>
int auto_register_factory()
{
F::template apply<T>();
return 0;
}

template<class T, class F>
struct auto_register
{
static int static_register_;
// This typedef ensures that the static member will be instantiated if
// the class itself is instantiated
typedef std::integral_constant<decltype(&static_register_), &static_register_> static_register_type_;
};

template<class T, class F>
int auto_register<T, F>::static_register_ = auto_register_factory<T, F>();

Then you can write your own CRTP class:

struct foo_register
{
template<class T>
static void apply()
{
// Do code when it encounters `T`
}
};

template<class Derived>
struct fooable : auto_register<Derived, foo_register>
{};

How can I maintain a compile-time list of all types derived from a base class?

Note: This answer was written before "the solution must be entirely compile-time and must work even if none of the types are instantiated." was added to the question.


Although this is not calculated at compile time, the name of each type is only added once. You must create at least one instance of each type to work (aka. types that are not instantiated will not be added to the container)

#include <iostream>
#include <vector>
#include <string>

struct TypeBase{
static std::vector<std::string> m_container;
};

std::vector<std::string> TypeBase::m_container;

template <typename DerivedType>
struct MyBase{
MyBase(){
static bool typeAdded = false;
if(!typeAdded){
typeAdded = true;
// you may also want to demangle this name
// take a look at boost/units/detail/utility.hpp
TypeBase::m_container.push_back(typeid(DerivedType).name());
}
}
};

struct Derived1 : public MyBase<Derived1>{

};

struct Derived2 : public MyBase<Derived2>{

};

struct Derived3 : public MyBase<Derived3>{

};

int main(){
Derived1 a, b, c;
Derived2 d, e, f;
Derived3 g, h, i;

for(std::string const & name : TypeBase::m_container){
std::cout << name << std::endl;
}

std::cin.get();
}

Using my compiler, this prints:

struct Derived1
struct Derived2
struct Derived3

create std::tuple using compile-time types and a run-time function

I am not sure if I correctly understand the question, because I dont understand why you want to iterate Idx, why use tuple_element or tuple_cat. I think you just want to call make_tuple to return a tuple whose elements are created via create_obj:

#include <tuple>

template<typename T>
T* create_obj() { return new T{};}

template<typename ...Ts>
auto create_tuple()
{
return std::make_tuple( create_obj<Ts>() ...);
}

struct A{};
struct B{};
struct C{};

int main() {
std::tuple<A*, B*, C*> a = create_tuple<A, B, C>();
}


Related Topics



Leave a reply



Submit