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<N>::foo(int)
called in numerical order. Splitting PossibleActions<1,4>()(a);
is a very 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> struct PossibleActions;
? But then the recursion is lost.
Or is it?
Related question: Is there a way to carry out recursion with Args... where some types are int but some are not (and with those that are not use recursion with the ints that define those types)? For example
PossibleActions<1, TwoAndThree, 4, EightAndTen, 20>()(a);
iterates through 1,2,3,4,8,10,20 as desired because TwoAndThree = PossibleActions<2,3>
and EightAndTen = PossibleActions<8,10>
??? If possible, that would solve the problem.