【问题标题】:Recursive inheritance with variadic templates使用可变参数模板的递归继承
【发布时间】:2015-01-03 18:10:00
【问题描述】:

考虑以下代码:

#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;
}

它运行除了这里是问题:任何可以调用ActionType&lt;2&gt;::foo(int) 也可以调用ActionType&lt;3&gt;::foo(int)。因此,每次我定义一个新类时,如果我使用 2 或 3,我必须在 PossibleActions&lt;I...&gt; 中同时使用两者。这对于维护当然是有问题的(比如我决定将来使用 2 也必须使用 3、7 和 20)。以下解决方案:

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);}
};

不可接受,因为我需要以 数字顺序 调用 ActionType&lt;N&gt;::foo(int)。拆分PossibleActions&lt;1,4&gt;()(a); 也是一个非常的糟糕解决方案,因为它遇到了相同的维护问题(我认为使维护变得更糟)。

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";} };

由于歧义而无法编译(并且使用虚拟继承没有帮助),我想不出其他任何东西。这个问题有解决办法吗?

也许用template &lt;typename... Args&gt; struct PossibleActions; 重新定义可能的操作?但是随后递归丢失了。

是吗?

相关问题: 有没有办法使用 Args 进行递归...其中某些类型是 int 但有些不是(以及那些不使用定义这些类型的 int 的递归)类型)?例如

PossibleActions<1, TwoAndThree, 4, EightAndTen, 20>()(a);

根据需要遍历 1,2,3,4,8,10,20,因为 TwoAndThree = PossibleActions&lt;2,3&gt;EightAndTen = PossibleActions&lt;8,10&gt;???如果可能,那将解决问题。

【问题讨论】:

  • 我不确定,但 virtual inheritance 可以帮助解决歧义吗?
  • 这是我尝试的第一件事,但是递归干扰了它,无法编译。但这并不意味着虚拟继承不起作用。
  • 也许你也应该在你的问题中指出这一点。不过,这是一种有趣的函数式编程方法。
  • @prestokeys 可能不是最有效的模板排序器,但how about this ?
  • @Piotr S. 很好的开始!所以想法是简单地遵循上述有缺陷的解决方案,但通过排序来纠正它。好的,那我们还是要泛化Sort&lt;T...&gt;,这样using OneAndFour = PossibleActions&lt;1,4&gt;; struct Widget : Sort&lt;0, OneAndFour, TwoAndThree, 5,8,13&gt; {};才能编译。我将尝试跟进您的第一个解决方案。

标签: c++ templates c++11 variadic


【解决方案1】:

这归功于 Piotr。 S 为这个解决方案(我希望我能给他加分,但他出于某种原因喜欢隐藏他的惊人之处)。虽然他的第二个解决方案也不错,但我更喜欢他的第一个解决方案提供的语法。他的 Sort 结构必须用

概括
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;
};

所以我为他这样做了。这允许语法

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

我更喜欢。我也在图片中添加了模板模板:

#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
}

但是请注意,如果原始包本身未排序,则该解决方案实际上会失败。在执行上述操作之前,这可能需要先在原始包上使用辅助排序器结构。

【讨论】:

  • 一个小提示:您可能应该在每个foo 成员函数中调用Sort&lt;PossibleActions&lt;1,4&gt;, TwoAndThree&gt;::type::operator()(a); 而不是Sort&lt;PossibleActions&lt;1,4&gt;, TwoAndThree&gt;::type()(a);,并从Sort&lt;&gt;::type 继承(嵌套名称说明符+::type
  • 这会提高性能,因为没有实例化发生?实际上这不会编译。它说没有对象就不能调用它。
  • 您当前正在创建一个新实例而不是调用基类的成员函数,它不应该这样做吗?
  • 很伤心,但 Visual Studio 2013(甚至 2015 Preview)无法运行该解决方案。我确定这是因为它无法推断包 Ks... 在 Merge&lt;T, S&lt;Is...&gt;, S&lt;&gt;, Ks...&gt;Merge&lt;T, S&lt;&gt;, S&lt;Js...&gt;, Ks...&gt; 的情况下开始的位置,此时它应该是可推断的。我可以通过为 Is...(或 Js...)放置单独的值来临时修复它,但它通常无法处理包,因此在修复错误之前无法使用它。跨度>
猜你喜欢
  • 1970-01-01
  • 2017-02-23
  • 1970-01-01
  • 2015-11-10
  • 1970-01-01
  • 2016-03-18
  • 2014-05-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多