4

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?

Ambroz Bizjak
  • 7,809
  • 1
  • 38
  • 49
  • I think it will be compiler dependent. Which compiler are you using? I did see something like that done at my last job; sadly, I don't remember the details. – abelenky Jun 06 '14 at 18:27
  • @abelenky I'm hoping for a standard C++11 solution, but I suppose I would go with anything that works with G++ 4.8. – Ambroz Bizjak Jun 06 '14 at 18:28
  • 2
    You might like my answer at: http://stackoverflow.com/questions/18701798/building-and-accessing-a-list-of-types-at-compile-time/18704609#18704609 –  Jun 06 '14 at 18:57
  • The major problem is that there's no way for a subsequent line to refer to any prior line, because they all have the same name and scope. Is it possible to give each line the name of the prior line, so we can build from prior results? – Mooing Duck Jun 06 '14 at 23:34
  • 1
    I'm not sure what you're trying to accomplish by that. but look at: http://journal.stuffwithstuff.com/2012/01/24/higher-order-macros-in-c/ this gives you the possibility to assign macros at instance time, might solve your problems. – Alexander Oh Jun 07 '14 at 06:59
  • @Alex That article is good, but not quite what I need. My use case for this is defining the set of configuration options for my software. Each option should be defined in one place, along with its data type and default value. Then each option (a type) needs to be passed as a template parameter to the class that uses its value, but all options also need to be passed to the configuration manager so it can save and restore them all at once. The list I'm building is for the configuration manager, to avoid boilerplate, since there are a lot of options. – Ambroz Bizjak Jun 07 '14 at 09:52
  • @AmbrozBizjak what are your requirements for the configuration manager? Is there anything against using a singleton to hold the values and perhaps it's own place to map defaults? Is your intention to keep the options close to the configuration manager or to keep it close to the owning entity? – Alexander Oh Jun 08 '14 at 00:42
  • @Alex My goal is that the configuration manager know about all the options at compile time. Why? I could say efficiency. Or maybe it's just because I like to play with C++ templates ;) It doesn't matter why, this question is not about that, and it's been solved :) – Ambroz Bizjak Jun 08 '14 at 06:36

3 Answers3

3

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>;
Manu343726
  • 13,969
  • 4
  • 40
  • 75
  • I know variadic templates, but I prefer cons lists because they are easier to work with and faster. Anyway, your answer doesn't provide a solution to my problem. As you've noted yourself, you can translate between the two list formats, so it doesn't matter whether I phrase the question in terms of cons lists of parameter pack lists. – Ambroz Bizjak Jun 06 '14 at 22:16
  • Note that a solution to my problem will involve an appropriate definition of the START_TO_LIST, ADD_TO_LIST and END_LIST macros. – Ambroz Bizjak Jun 06 '14 at 22:19
  • 4
    @AmbrozBizjak: Cons list are harder to work with, and slower. You're just not used to variadics. – Mooing Duck Jun 06 '14 at 23:35
  • @MooingDuck: My testing some time ago with G++ showed that simple recursive decomposition of a variadic template has quadratic performance. My reasoning is that each time a template is instantiated with a parameter pack, the whole pack is copied. With cons list, the tail is simply taken. – Ambroz Bizjak Jun 06 '14 at 23:44
  • @AmbrozBizjak: With Clang++ that is most definitely not that case, but I suppose I don't know for certain about G++. – Mooing Duck Jun 06 '14 at 23:54
  • @MooingDuck Besides variadic-template based typelists are far easier to use and implement, they have a property which head-tail approach doesn't: **They might use O(1) instantation depth for certain operations, relying on pack expansion**. From my own experience (Where not reaching max instantation depth is one of the key goals) this huge makes the difference. – Manu343726 Jun 07 '14 at 12:07
  • 1
    Consider the implementation of a `transform` metafunction (`map` for functional programmers): With variadic templates, its only a pack expansion of the function application, with head:tail you have to write the hole recursive trasversal by hand. (I know this very well, I'm currently writting algorithms both for variadic-based typelists and iterator ranges :P https://github.com/Manu343726/Turbo/blob/reboot/algorithm.hpp) – Manu343726 Jun 07 '14 at 12:11
  • For anyone claiming that variadic templates are faster, try compiling these: http://ideone.com/JTBjjT and http://ideone.com/lb07lI. They build a list of integers then compute the length of said list. The first (variadic) version compiles much more slowly, and it seems quadratic. This is both with clang++ and g++. Actually, I couldn't test with clang++ on large lists because it segfaults. – Ambroz Bizjak Jun 07 '14 at 14:04
1

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> > > >
Kan Li
  • 8,557
  • 8
  • 53
  • 93
  • The issue with this is that I can't insert arbitrary definitions in between the ADD_TO_LIST invocations. – Ambroz Bizjak Jun 06 '14 at 23:26
  • That is so much simipler than mine: http://coliru.stacked-crooked.com/a/d7f802f3f225f476 – Mooing Duck Jun 06 '14 at 23:31
  • 1
    @AmbrozBizjak: You want to add arbitrary definitions between? That's a big deal, and should be clarified in the question, preferably in the sample. (Also, that moves this into the realm of the impossible) – Mooing Duck Jun 06 '14 at 23:32
  • @AmbrozBizjak, in your answer, ADD_TO_LIST contains a template specialization and it can't be declared in block scope. i.e. your macros can't be used in function. If we are just want to declare the type in global scope, then there are ways that don't have the limitation in your solution. – Kan Li Jun 07 '14 at 01:19
  • @icando Right, I don't need this to work in functions, only in global and maybe class scope (class scope would need a dummy template parameter). So, what other way is there? – Ambroz Bizjak Jun 07 '14 at 01:22
  • @AmbrozBizjak, I've updated my answer now, it works for global, class and function scope. C++ is a magic! – Kan Li Jun 07 '14 at 06:54
  • Nice improvement! I've made is slightly simpler if you already have list reversal implemented. Also, I prefer Type() to declval here, it's shorter, and constructor always exists for these meta types. http://ideone.com/fCofNq – Ambroz Bizjak Jun 07 '14 at 12:35
0

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, "");
Ambroz Bizjak
  • 7,809
  • 1
  • 38
  • 49
  • ADD_TO_LIST contains a template specialization and it can't be declared in block scope. i.e. your macros can't be used in function. If we are just want to declare the type in global scope, then there are ways that don't have the limitation in your solution. – Kan Li Jun 07 '14 at 01:15
  • I've updated my answer now, it works for global, class and function scope. C++ is a magic! – Kan Li Jun 07 '14 at 06:54