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)
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> > > >
You could use C++11 variadic templates directly, they allow to write typelists in a much more fancy way than the classic functional head:tail
approach:
template<typename... Ts>
struct list{};
using l = list<int,char,bool>;
On the other hand, if you like the head-tail way you could translate from one format to the other. In this case (Variadic to functional):
template<typename HEAD , typename TAIL>
struct list{};
struct nil{};
template<typename... Ts>
struct make_list;
template<typename HEAD , typename... TAIL>
struct make_list<HEAD,TAIL>
{
using result = list<HEAD,typename make_list<TAIL...>::result;
};
template<>
struct make_list<>
{
using result = nil;
};
An example:
//l is list<int,list<char,list<bool,nil>>>
using l = typename make_list<int,char,bool>::result;
Of course you could use template aliases to make the syntax more clear:
template<typename... Ts>
using make = typename make_list<Ts...>::result;
using l = make<int,char,bool>;
I've found a solution! Though it's just a bit more limited; you need to provide a unique name for each element, and there needs to be an upper limit on the number of elements (here 100).
#include <type_traits>
// Good old Cons and Nil.
template <typename THead, typename TTail>
struct Cons {
using Head = THead;
using Tail = TTail;
};
struct Nil {};
// Helper template which builds a list from a template
// providing elements at given indices.
template <template<int> class ElemAt, int Offset, int Length>
struct BuildListFromElemAt {
using Result = Cons<typename ElemAt<Offset>::Elem, typename BuildListFromElemAt<ElemAt, (Offset + 1), (Length - 1)>::Result>;
};
template <template<int> class ElemAt, int Offset>
struct BuildListFromElemAt<ElemAt, Offset, 0> {
using Result = Nil;
};
// This is where the abuse begins.
// We use these classes to keep the current length
// of the list, in combination with function overloads.
// Each time we add an element, we will provide a more
// specific overload of a helper function.
template <int N>
struct Counter : public Counter<(N - 1)> {
static int const Num = N;
};
template <>
struct Counter<0> {
static int const Num = 0;
};
// At the beginning, we define the initial size function
// taking Counter<0>, and declare the ElemAt template.
#define BEGIN_LIST(ListName) \
Counter<0> ListName##_Size (Counter<0>); \
template <int Index> struct ListName##_ElemAt;
// For each element, we retrieve the current length of the
// list by checking the return type of calling the size function
// with a very large Counter.
// Then we overload the size function for one greater Counter,
// which ensures that we get an incremented length at the next
// step. We also define the ElemAt for this index to return the
// given Value.
#define ADD_TO_LIST(ListName, Name, Value) \
static int const ListName##_##Name##_PrevLen = decltype(ListName##_Size(Counter<100>()))::Num; \
static int const ListName##_##Name##_Len = ListName##_##Name##_PrevLen + 1; \
Counter<ListName##_##Name##_Len> ListName##_Size (Counter<ListName##_##Name##_Len>); \
template <> struct ListName##_ElemAt<ListName##_##Name##_PrevLen> { \
using Elem = Value; \
};
// At the end, we retrieve the final length of the list,
// and build the list using the BuildListFromElemAt template.
#define END_LIST(ListName) \
static int const ListName##_Len = decltype(ListName##_Size(Counter<100>()))::Num; \
using ListName = typename BuildListFromElemAt<ListName##_ElemAt, 0, ListName##_Len>::Result;
// Here we go...
BEGIN_LIST(List)
ADD_TO_LIST(List, First, int)
ADD_TO_LIST(List, Second, float)
ADD_TO_LIST(List, Third, double)
END_LIST(List)
// And it works :)
static_assert(std::is_same<typename List::Head, int>::value, "");
static_assert(std::is_same<typename List::Tail::Head, float>::value, "");
static_assert(std::is_same<typename List::Tail::Tail::Head, double>::value, "");