简体   繁体   中英

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<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.

Credit goes to Piotr. S for this solution (I wish I could offer him points, but he likes to hide his amazingness for some reason). Though his second solution is nice too, I prefer the syntax offered by his first solution. His Sort struct had to be generalized with

template <typename, typename...> struct Sort;

template <typename T, typename A, typename B>
struct Sort<T,A,B> {
    using type = typename Merge<T,A,B>::type;
};

template <typename T, typename First, typename Second, typename... Rest>
struct Sort<T, First, Second, Rest...> {
    using type = typename Sort<T, typename Sort<T, First, Second>::type, Rest...>::type;
};

so I did that for him. This allows the syntax

struct Widget : Sort<PossibleActions<0,5>, OneAndFour, TwoAndThree>

which I like better. I added template-templates into the picture too:

#include <iostream>

namespace Detail {
    template <typename T, typename, typename, T...> struct Merge;

    template <typename T, template <T...> class S, T... Ks>
    struct Merge<T, S<>, S<>, Ks...> {
        using type = S<Ks...>;
    };

    template <typename T, template <T...> class S, T... Is, T... Ks>
    struct Merge<T, S<Is...>, S<>, Ks...> {
        using type = S<Ks..., Is...>;
    };

    template <typename T, template <T...> class S, T... Js, T... Ks>
    struct Merge<T, S<>, S<Js...>, Ks...> {
        using type = S<Ks..., Js...>;
    };

    template <typename T, bool, typename, typename, T...> struct Strip;

    template <typename T, template <T...> class S, T I, T... Is, T J, T... Js, T... Ks>
    struct Strip<T, true, S<I, Is...>, S<J, Js...>, Ks...> {
        using type = Merge<T, S<I, Is...>, S<Js...>, Ks..., J>;
    };

    template <typename T, template <T...> class S, T I, T... Is, T J, T... Js, T... Ks>
    struct Strip<T, false, S<I, Is...>, S<J, Js...>, Ks...> {
        using type = Merge<T, S<Is...>, S<J, Js...>, Ks..., I>;
    };

    template <typename T, template <T...> class S, T I, T... Is, T J, T... Js, T... Ks>
    struct Merge<T, S<I, Is...>, S<J, Js...>, Ks...> : Strip<T, (I > J), S<I, Is...>, S<J, Js...>, Ks...>::type {};

    template <typename, typename...> struct Sort;

    template <typename T, typename A, typename B>
    struct Sort<T,A,B> {
        using type = typename Merge<T,A,B>::type;
    };

    // Piotr S.'s Sort generalized to accept any number of template arguments.
    template <typename T, typename First, typename Second, typename... Rest>
    struct Sort<T, First, Second, Rest...> {
        using type = typename Sort<T, typename Sort<T, First, Second>::type, Rest...>::type;
    };
}

template <typename... P>
using Sort = typename Detail::Sort<int, P...>::type;

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 <> struct ActionType<5> : ActionOption {
    virtual void foo(int) const override {std::cout << "ActionType<5>::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);
    }
};

using OneAndFour = PossibleActions<1,4>;
using TwoAndThree = PossibleActions<2,3>;

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

struct Object : Sort<PossibleActions<1,4>, TwoAndThree> {
    void foo(int a) {Sort<PossibleActions<1,4>, TwoAndThree>()(a);}
};

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

struct Widget : Sort<PossibleActions<0,5>, OneAndFour, TwoAndThree> {
    void foo(int a) {Sort<PossibleActions<0,5>, OneAndFour, TwoAndThree>()(a);} 
};

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

    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;

    Widget widget;
    widget.foo(12);  // ActionType<0>::foo(int) called  ActionType<1>::foo(int) called   ActionType<2>::foo(int) called   ActionType<3>::foo(int) called   ActionType<4>::foo(int) called   ActionType<5>::foo(int) called
}

Note however, the solution actually fails if the original packs are themselves not sorted. This will probably require a helper sorter struct to use first on the original packs before carrying out the above.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM