There are two problems here. The first is getting it to compile. The second is to be able to call doSomething
.
The easy solution to the first problem is:
template <class T>
class MyClassFunction {
public:
virtual void doSomething(T object) = 0;
};
template <typename... TypesT>
class MyClass : public MyClassFunction<TypesT>...
{
uint8_t field[maxSizeOf<TypesT...>()];
};
this has the disadvantage that calls to doSomething
can be hard to do:
prog.cpp:33:59: error: request for member ‘doSomething’ is ambiguous
using foo = decltype( ((MyClass<int, char>*)nullptr)->doSomething(7) );
^~~~~~~~~~~
we need some using
s to fix this. In c++17 we could just do:
template <typename... TypesT>
class MyClass : public MyClassFunction<TypesT>...
{
uint8_t field[maxSizeOf<TypesT...>()];
public:
using MyClassFunction<TypesT>::doSomething...;
};
but that isn't available in c++11.
To fix this we have to do a tree-based inheritance. The easiest tree-based is linear:
template<class...Ts>
struct MyClassFunctions {};
template<class T0, class T1, class...Ts>
struct MyClassFunctions<T0, T1, Ts...>:
MyClassFunction<T0>, MyClassFunctions<T1, Ts...>
{
using MyClassFunction<T0>::doSomething;
using MyClassFunctions<T1, Ts...>::doSomething;
};
template<class T0>
struct MyClassFunctions:MyClassFunction<T0> {};
template <typename... TypesT>
class MyClass : public MyClassFunctions<TypesT...>
{
uint8_t field[maxSizeOf<TypesT...>()];
};
Live example.
This has the disadvantage of creating O(n^2) total type name length, which can cause problems for long lists of types. At medium length you get memory bloat and compile time slowdown, at long length you get compilers crashing.
To get around that you can build a binary tree of inheritance. The trick is to be able to split a ...
pack in half using log-depth template recursion. Once you have that, the code becomes:
template<class T0, class T1, class...Ts>
struct MyClassFunctions<T0, T1, Ts...>:
left_half< MyClassFunctions, T0, T1, Ts... >,
right_half< MyClassFunctions, T0, T1, Ts... >
{
using left_half< MyClassFunctions, T0, T1, Ts... >::doSomething;
using right_half< MyClassFunctions, T0, T1, Ts... >::doSomething;
};
however this effort is only worthwhile if you have more than a few dozen types being passed in.
left/right half look like this:
template<template<class...>class Z, class...Ts>
using left_half = /* todo */;
template<template<class...>class Z, class...Ts>
using right_half = /* todo */;
with some crazy metaprogramming in todo.
You can use the indexes trick and the machinery of std::tuple
to split those lists (in c++11 log-depth index generation takes a bit of effort). Or you can do an exponential split on the type list.
Write
template<class...Ts>
struct pack {};
template<std::size_t N, class Pack>
struct split/* {
using lhs = // part before N
using rhs = // part after N
};
that splits a type list with the first N
being on the left. It can be written recursively:
template<std::size_t N, class...Ts>
struct split<N, pack<Ts...>> {
private:
using half_split = split<N/2, pack<Ts...>>;
using second_half_split = split<N-N/2, typename half_split::rhs>;
public:
using lhs = concat< typename half_split::lhs, typename second_half_split::lhs >;
using rhs = typename second_half_split::rhs;
};
template<class...Ts>
struct split<0, pack<Ts...>> {
using lhs=pack<>;
using rhs=pack<Ts...>;
};
template<class T0, class...Ts>
struct split<1, pack<T0, Ts...>> {
using lhs=pack<T0>;
using rhs=pack<Ts...>;
};
this requires concat<pack, pack>
to do the obvious thing.
Now you need apply<template, pack>
and then write left_half
and right_half
.