I need to find a way to recursively build a class given a set of template arguments so that the class inherits from itself and build a method f
for the current first template argument in the list of template arguments and then inherits from itself by passing on the rest of the list.
So, basically I want to achieve the following interface for a class C
:
C<T1, T2, T3> c;
c
now has methods C::f(T1)
, C::f(T2)
and C::f(T3)
My approach so far was like this:
// primary template
template <class H, class...>
class C {};
// base case where class... is empty
template <class H, class...>
class C<H>
{
public:
void f(const H& h){
// std::cout << typeid(h).name() << "\n";
}
};
// recursive case where T is nonempty
template <class H, class... T>
class C : public C<T...>
{
public:
void f(const H& h){
// std::cout << typeid(h).name() << "\n";
}
};
This does not actually compile, as I get
error: redefinition of 'C' class C : public C
Is my approach basically possible and just a matter of semantically and or syntactically invalid code or does this approach not work in principle?
For starters, a class cannot inherit from itself.
Secondly, all that you apparently are trying to accomplish is to have each template parameter generate a class method that takes that class as a parameter.
In which case, something like this should work.
template<typename ...> class C;
template<>
class C<> {};
template<typename T, typename ...Args>
class C<T, Args...> : public C<Args...> {
public:
void f (const T &)
{
// Whatever...
}
};
Note that this is not a class inheriting from itself. It's a template instance that inherits from another template instance. Each template instance is a unique class.
Note that you have a single definition of the class method in question here, and not two, as you were trying to do. This is a slight improvement.
Another improvement would be to consider rearranging the class hierarchy in this manner, if it's possible to do this taking into consideration your other class requirements:
template<typename T> class F {
public:
void f (const T &)
{
}
};
template<typename ...> class C;
template<>
class C<> {};
template<typename T, typename ...Args>
class C<T, Args...> : public C<Args...> , public F<T> {
};
With this approach, whether you use C<int, float>
, or C<int, char *>
, the class method will always be declared to be a method of F<int>
. This cuts down slightly on the resulting code float, since any instance of C
that includes int
, for example, will generate a single class method instance, instead of two separate methods like C<int, float>::f(const int &)
and C<int, char *>::f(const int &)
, which would, otherwise, be completely identical.
As an alternative approach, I'm proposing a solution based on mixins. Feel free to ignore the boilerplate introduced by class type_name
, the purpose of which is to show you that the right part is picked up on a per argument base.
It follows a minimal, working example:
#include<type_traits>
#include<utility>
#include<iostream>
template<typename> struct type_name;
template<> struct type_name<int> { static const char *name; };
template<> struct type_name<double> { static const char *name; };
template<> struct type_name<char> { static const char *name; };
const char * type_name<int>::name = "int";
const char * type_name<double>::name = "double";
const char * type_name<char>::name = "char";
template<typename T>
struct Part {
void func(T t) {
std::cout << type_name<T>::name << ": " << t << std::endl;
}
};
template<typename... T>
struct S: private Part<T>... {
template<typename... Args>
void f(Args&&... args) {
using acc_t = int[];
acc_t acc = { 0, (Part<std::decay_t<Args>>::func(std::forward<Args>(args)), 0)... };
(void)acc;
}
};
int main() {
S<int, double, char> s;
s.f(42);
s.f(0.1);
s.f('c');
s.f('a', 0.3, 23);
}
Some plus of this method:
Part<T>
is defined only once for any T
, no matter how many times you use it in different packs.
S<T, T>
is rejected at compile-time and more in general all those packs that contain the same type two or more times. They would otherwise give births to multiple definitions of f(T)
and a subsequent call would be probably ambiguous.
You can invoke f
with a single parameter, as requested. Anyway, as shown in the example, you can invoke f
with N
parameters and the call is equivalent to N
calls to f
with a single parameter.
In other terms, you can either use this:
s.f('a', 0.3, 23);
Or this:
s.f('a');
s.f(0.3);
s.f(23);
The result will be the same in both cases.
This feature can be easily turned off if needed by defining S
as it follows:
template<typename... T>
struct S: private Part<T>... {
template<typename U>
void f(U &&u) {
Part<std::decay_t<U>>::func(std::forward<U>(u));
}
};
See it running on wandbox.
As a side note, this is the usual trick used to emulate fold expressions in C++14:
template<typename... Args>
void f(Args&&... args) {
using acc_t = int[];
acc_t acc = { 0, (Part<std::decay_t<Args>>::func(std::forward<Args>(args)), 0)... };
(void)acc;
}
You can find more about that on SO as well as on the web.