samedi 3 janvier 2015

Recursive inheritance with variadic templates

Consider the following code:



#include <iostream>

struct ActionOption {
virtual void foo(int) const = 0;
};

template <int> struct ActionType;

template <> struct ActionType<0> : ActionOption {
virtual void foo(int) const override {std::cout << "ActionType<0>::foo(int) called.\n";}
};

template <> struct ActionType<1> : ActionOption {
virtual void foo(int) const override {std::cout << "ActionType<1>::foo(int) called.\n";}
};

template <> struct ActionType<2> : ActionOption {
virtual void foo(int) const override {std::cout << "ActionType<2>::foo(int) called.\n";}
};

template <> struct ActionType<3> : ActionOption {
virtual void foo(int) const override {std::cout << "ActionType<3>::foo(int) called.\n";}
};

template <> struct ActionType<4> : ActionOption {
virtual void foo(int) const override {std::cout << "ActionType<4>::foo(int) called.\n";}
};

template <int...> struct PossibleActions;

template <> struct PossibleActions<> { void operator()(int) const {} };

template <int First, int... Rest>
struct PossibleActions<First, Rest...> : ActionType<First>, PossibleActions<Rest...> {
void operator()(int a) const {
ActionType<First>::foo(a);
PossibleActions<Rest...>::operator()(a);
}
};

// Anything that can call ActionType<2>::foo(int) can also call ActionType<3>::foo(int).
struct Object : PossibleActions<1, 2,3, 4> {
void foo(int a) {PossibleActions<1,2,3,4>()(a);}
};

struct Blob : PossibleActions<0, 2,3, 4> {
void foo(int a) {PossibleActions<0,2,3,4>()(a);}
};

int main() {
Object object;
object.foo(12); // ActionType<1>::foo(int) called ActionType<2>::foo(int) called ActionType<3>::foo(int) called ActionType<4>::foo(int) called
std::cout << std::endl;

Blob blob;
blob.foo(12); // ActionType<0>::foo(int) called ActionType<2>::foo(int) called ActionType<3>::foo(int) called ActionType<4>::foo(int) called
std::cout << std::endl;
}


It runs except here is the problem: anything that can call ActionType<2>::foo(int) can also call ActionType<3>::foo(int). Thus every time I define a new class, if I use 2 or 3 I have to use both in PossibleActions<I...>. This is problematic for maintenance of course (say I decide in the future that using 2 must also use 3, 7, and 20). The following solution:



using TwoAndThree = PossibleActions<2,3>;
struct Object : PossibleActions<1,4>, TwoAndThree {
void foo(int a) {PossibleActions<1,4>()(a); TwoAndThree()(a);}
};

struct Blob : PossibleActions<0,4>, TwoAndThree {
void foo(int a) {PossibleActions<0,4>()(a); TwoAndThree()(a);}
};


is not acceptable because I need ActionType::foo(int) called in numerical order. Splitting PossibleActions<1,4>()(a); a is poor solution too because it runs into the same maintenaince problem (makes maintenance even worse I think).



template <> struct ActionType<2> : ActionOption { virtual void foo(int) const override {std::cout << "ActionType<2>::foo(int) called.\n";} };
template <> struct ActionType<3> : ActionType<2> { virtual void foo(int) const override {std::cout << "ActionType<3>::foo(int) called.\n";} };


does not compile due to ambiguity (and using virtual inheritance did not help), and I can't think of anything else. Is there a solution to this problem? Perhaps redefine PossibleActions with template <typename... Args>? But then the recursion is lost. Or is it? Is there a way to carry out recursion with Args... for only those in Args... that are int and skip those types that are not int???


Aucun commentaire:

Enregistrer un commentaire