4

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.

prestokeys
  • 4,817
  • 3
  • 20
  • 43
  • I'm not sure, but could [virtual inheritance](http://stackoverflow.com/questions/21558/in-c-what-is-a-virtual-base-class) help, to resolve the ambiguities? – πάντα ῥεῖ Jan 03 '15 at 18:26
  • That's the first thing I tried, but it the recursion interfered with it, failing to compile. Doesn't mean that virtual inheritance won't work though. – prestokeys Jan 03 '15 at 18:27
  • May be you should point this out in your question as well. Though, that's an interesting approach to functional programming what you're asking about. – πάντα ῥεῖ Jan 03 '15 at 18:30
  • @prestokeys probably not the most efficient template sorter, but [how about this](http://coliru.stacked-crooked.com/a/82575954a28c98f9) ? – Piotr Skotnicki Jan 03 '15 at 18:51
  • @Piotr S. Excellent start! So the idea is to simply follow the above flawed solution but correct it by sorting. Ok, then we still have to generalize `Sort` so that `using OneAndFour = PossibleActions<1,4>; struct Widget : Sort<0, OneAndFour, TwoAndThree, 5,8,13> {};` will compile. I will try to follow up on your first solution. – prestokeys Jan 03 '15 at 19:52
  • You probably want to use Boost.MPL to make you life easier. The MPL has a sort algorithm `http://www.boost.org/doc/libs/1_57_0/libs/mpl/doc/refmanual/sort.html` to sort template compile-time sequences. Then you can hide the unsavoury MPL details behind a custom template wrapper. – Lærne Jan 03 '15 at 19:58
  • @prestokeys [here is my final solution](http://coliru.stacked-crooked.com/a/c34a0db8455b7101), you may find it useful – Piotr Skotnicki Jan 03 '15 at 20:01
  • Not sure I understood or not, but the first thing that came to my mind is to create a typelist (Modern C++ Design) out of the integer values using an Int2Type class and inherit them in the order of increasing/decreasing values. Using typelist, you can create a heirarchy based upon the inheritance structure and iterate over it. Google for "loki" library. – Arunmu Jan 03 '15 at 20:07
  • `constexpr` is your friend, and C++14 makes it easy. Do you have 14? Maintain the dependencies as overloads of some function, expand and keep sorted a set of implied numbers. And yes, boost mpl would help. Basically write code to enforce invariants on the list of numbers, either via checks (and compile time failure) or auto injection of more numbers (and closure over the injection rules) – Yakk - Adam Nevraumont Jan 03 '15 at 20:08
  • @prestokeys side note: you can't mix type and non-type template parameters, but you can change `merge<0, OneAndFour, TwoAndThree, 5,8,13>` into `merge, merge,merge<8,13>>>`, or write a `merge_list` helper: `merge_list, OneAndFour, TwoAndThree, PossibleActions<5,8,13>>` – Piotr Skotnicki Jan 03 '15 at 20:35

1 Answers1

0

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.

prestokeys
  • 4,817
  • 3
  • 20
  • 43
  • a minor note: you should probably be calling `Sort, TwoAndThree>::type::operator()(a);` instead of `Sort, TwoAndThree>::type()(a);` in each `foo` member function, and inherit from `Sort<>::type` (nested name specifier+`::type`) – Piotr Skotnicki Jan 03 '15 at 21:22
  • This would increase the performance because no instantiation happens? Actually that does not compile. Its says it cannot be called without object. – prestokeys Jan 03 '15 at 21:24
  • You are currently creating a new instance instead of calling a base class' member function, isn't it supposed to do so? – Piotr Skotnicki Jan 03 '15 at 21:29
  • So sad, but Visual Studio 2013 (and even 2015 Preview) cannot run the solution. I determined it is because it cannot deduce where the pack Ks... begins in the `Merge, S<>, Ks...>` and `Merge, S, Ks...> ` cases, when it should be deducible. I was able to fix it temporarily by placing individual values for the Is... (or Js...), but it cannot handle the pack in general, so there is no way to use it until they fix their bug. – prestokeys Jan 04 '15 at 01:09