In C++, is there a way to build a compile time list incrementally, in the following pattern?
START_LIST(List)
ADD_TO_LIST(List, int)
ADD_TO_LIST(List, float)
ADD_TO_LIST(List, double)
END_LIST(List)
The result of this should be equivalent to:
using List = Cons<int, Cons<float, Cons<double, Nil>>>;
I also have a restriction that the space between the macros needs to be at whatever scope the whole thing is. I'm planning to define things and register them with the list at the same time using a macro, something like this:
#define DEFINE_ELEMENT(Name, Value) \
using Name = SomeTemplate<Value>; \
ADD_TO_LIST(ListOfElements, Name)
In other words, it is not permitted to define START_LIST
to something like SomeTemplate<
or decltype(
. That would make it impossible to add new definitions in between.
Note that the solution can alternatively be in the form of a "parameter pack" (variadic template) list. I only care that it follows the incremental definition pattern as shown above.
Could specializations be used here? If it's not possible exactly with the above pattern, is it possible with a bit more boilerplate?
In OP's own solution, it only works for global scope, not class scope, nor function scope. My implementation here works for all of global, class and function scope. Another advantage over OP's solution is my solution allow multiple list START_LIST/END_LIST pairs overlap, i.e. different list constructions can be interleaving.
One small limitation is it uses __COUNTER__
macro, which is not part of starndard, but it is well supported by gcc, clang and MSVC, so portability is not a big issue here. Another thing is for function scope, it has to use a separate macro START_LIST_FUNC
and ADD_TO_LIST_FUNC
as I make use function overload resolution but in function scope it can't declare static
function, while at class level it has to use static
function.
EDIT: incorporate the idea of ListReverseHelper from OP's comment to make it much simpler.
#include <iostream>
#include <typeinfo>
using namespace std;
struct Nil {};
template <typename T, typename U> struct Cons {};
template <typename List, typename Reversed> struct ListReverseHelper;
template <typename Reversed>
struct ListReverseHelper<Nil, Reversed> {
using Type = Reversed;
};
template <typename Head, typename Tail, typename Reversed>
struct ListReverseHelper<Cons<Head, Tail>, Reversed> {
using Type = typename ListReverseHelper<Tail, Cons<Head, Reversed>>::Type;
};
template <typename T, int N> struct ListMakerKey : ListMakerKey<T, N-1> {};
template <typename T> struct ListMakerKey<T, 0> {};
#define START_LIST_(name, modifier) \
struct name##_ListMaker {}; \
modifier Nil list_maker_helper_(ListMakerKey<name##_ListMaker, __COUNTER__>);
#define ADD_TO_LIST_(name, type, modifier) \
modifier Cons<type, decltype(list_maker_helper_(ListMakerKey<name##_ListMaker, __COUNTER__>{}))> \
list_maker_helper_(ListMakerKey<name##_ListMaker, __COUNTER__>);
#define END_LIST(name) \
using name = typename ListReverseHelper<decltype(list_maker_helper_(ListMakerKey<name##_ListMaker, __COUNTER__>{})), Nil>::Type;
#define START_LIST(name) START_LIST_(name, static)
#define ADD_TO_LIST(name, type) ADD_TO_LIST_(name, type, static)
#define START_LIST_FUNC(name) START_LIST_(name,)
#define ADD_TO_LIST_FUNC(name, type) ADD_TO_LIST_(name, type,)
START_LIST(List)
ADD_TO_LIST(List, int)
int a = 10;
ADD_TO_LIST(List, float)
int b = 10;
START_LIST(List2)
ADD_TO_LIST(List, int)
int c = 10;
ADD_TO_LIST(List2, float)
ADD_TO_LIST(List, double)
ADD_TO_LIST(List2, int)
ADD_TO_LIST(List2, float)
END_LIST(List2)
ADD_TO_LIST(List, double)
ADD_TO_LIST(List, char)
END_LIST(List)
struct A {
START_LIST(List3)
ADD_TO_LIST(List3, int)
int a = 10;
ADD_TO_LIST(List3, float)
int b = 10;
ADD_TO_LIST(List3, double)
ADD_TO_LIST(List3, int)
END_LIST(List3)
};
int main() {
START_LIST_FUNC(List4)
ADD_TO_LIST_FUNC(List4, char)
int a = 10;
ADD_TO_LIST_FUNC(List4, float)
int b = 10;
ADD_TO_LIST_FUNC(List4, int)
ADD_TO_LIST_FUNC(List4, char)
END_LIST(List4)
List x;
List2 y;
A::List3 z;
List4 w;
cout << typeid(x).name() << endl;
cout << typeid(y).name() << endl;
cout << typeid(z).name() << endl;
cout << typeid(w).name() << endl;
}
Compiled under g++-4.8:
[hidden]$ g++ -std=c++11 x.cpp && c++filt -t `./a.out`
Cons<int, Cons<float, Cons<int, Cons<double, Cons<double, Cons<char, Nil> > > > > >
Cons<float, Cons<int, Cons<float, Nil> > >
Cons<int, Cons<float, Cons<double, Cons<int, Nil> > > >
Cons<char, Cons<float, Cons<int, Cons<char, Nil> > > >