Reputation: 3048
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 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
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
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
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