Lukas Barth
Lukas Barth

Reputation: 3048

Dynamically build a template parameter pack

I'm trying to have my code call a method on a list of classes, and I want to be able to configure this list at compile time. To make matters more clear (I hope), this is approximately what I'm currently doing:

template <class...> class pack {};

class A {
public:
    static void foo() {};
};
class B {
public:
    static void foo() {};
};
class C {
public:
    static void foo() {};
};

using ClassList = pack<A, B, C>;

template<class T, class ...Remaining>
void do_something(pack<T, Remaining...>) {
  // do something with T
  T::foo();
  do_something(pack<Remaining...>());
}

void do_something(pack<>) {
  // do nothing, recursion ends
}

int main() {
  do_something(ClassList());
}

Basically, it calls do_something() witch each of A, B and C as template argument T.

Here's the twist: I want to be able to dynamically en- or disable A, B and C based on '#ifdef's. I could obviously use something like

#if defined(USE_A) && defined (USE_B) && defined (USE_C)
using ClassList = pack<A, B, C>;
#elif defined(USE_A) && defined (USE_B)
using ClassList = pack<A, B>;
…

but that results in 2^n statements (if n is the number of potential classes).

My Solution Attempt

My solution attempt would be to have for each class T that I want to add to the class list an 'AddT' class that derives from pack, matches the parameter pack "inside" the pack class in its template definition, and then adds T. Something along the lines of:

using BaseClassList = pack<A, B>;

#ifdef USE_C

template <template <class ...Others> class oldpack>
class AddC : public pack<C, Others...> {};
using ClassesWithC = AddC<BaseClassList>; 

#else

using ClassesWithC = BaseClassList;

#endif

However, it looks like the 'inner' template parameter (pack) 'Others' is not accessible when defining AddC, the error I get is:

error: 'Others' was not declared in this scope

Am I doing something completely stupid here? Is there a nicer way of doing it? Or can the problem above be fixed? I'd happily use C++14 (or even C++1z, as far as it's implemented by a reasonable version of GCC and clang), if that helps in any way.

Upvotes: 2

Views: 478

Answers (3)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275385

Exploit the whitespace flexibility of C++ and check for each separately.

template<class, class...Ts>
using pack_helper = pack<Ts...>;

using ClassList = pack_helper<void
#ifdef USE_A
  ,A
#endif
#ifdef USE_B
  ,B
#endif
#ifdef USE_C
  ,C
#endif
>;

pack_helper drops its first type, then generates a pack with the rest. We pass void for it.

We do this so each of the other pack elements can all start with a comma, including the first one.


But that isn't as fun as it could be.

template<class...>struct pack{using type=pack; constexpr pack(){}};
template<class...Ts>constexpr pack<Ts...> pack_v{};

template<class...Ts, class...Us>
constexpr pack<Ts...,Us...>
operator+( pack<Ts...>, pack<Us...> )
{ return {}; }

constexpr auto Classes = pack_v<>
#ifdef USE_A
  +pack_v<A>
#endif
#ifdef USE_B
  +pack_v<B>
#endif
#ifdef USE_C
  +pack_v<C>
#endif
;
using ClassList = decltype(Classes);

in which we add together type packs to get our answer.

In C++11, replace pack_v<?> with pack<?>{}.

Upvotes: 11

Walter
Walter

Reputation: 45434

Just add them one after the other

using ClassList = pack<void
#ifdef USE_A
                      ,A
#endif
#ifdef USE_B
                      ,B
#endif
#ifdef USE_C
                      ,C
#endif
                        >;

The void is necessary so that the comma works with the first non-void parameter. There are two ways to deal with this first void element. Either amend your do_something to do nothing for T=void or remove it using a helper template as explained in Yakk's answer.

Upvotes: 2

Jarod42
Jarod42

Reputation: 217275

How about something like:

using ClassList = pack<
#if defined(USE_A) 
    A,
#endif
#if defined (USE_B)
    B,
#endif
#if defined (USE_C)
    C,
#endif
    struct dummy // For comma
>;

And then provide an helper class to drop last element.

Upvotes: 5

Related Questions