【问题标题】:Meta-iteration over variadic templates arguments可变参数模板参数的元迭代
【发布时间】:2016-09-20 05:37:13
【问题描述】:

我想概括以下模式:

template<class A1, class A2, class A3>
class Foo {
protected:
  template<class T>
  void foo(const T& t) {...do stuff...}
public:
  void bar(const A1& a) { foo(a); }
  void bar(const A2& a) { foo(a); }
  void bar(const A3& a) { foo(a); }
};

上述方法不能随着参数数量的增加而扩展。所以,我想做:

template<class As...>
class Foo {
protected:
  template<class T>
  void foo(const t& a) {...do stuff...}
public:
  for each type A in As declare:
  void bar(const A& a) { foo(a); }
};

有办法吗?

【问题讨论】:

  • 可变参数序列,是否可以多次包含相同的类型,还是该序列是唯一类型?

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


【解决方案1】:

另一种方法是检查bar 以测试类型是否在序列中,否则会显示有用的错误消息,这样可以避免任何继承技巧..

#include <iostream>

struct E {};
struct F {};

template <class... As>
class Foo
{
    template <typename U>
    static constexpr bool contains() {
        return false;
    }

    template <typename U, typename B, typename ...S>
    static constexpr bool contains() {
        return (std::is_same<U, B>::value)? true : contains<U, S...>();
    }

protected:
    template <class T>
    void foo(const T& a) { std::cout << __PRETTY_FUNCTION__ << std::endl; }

public:
    template <class T>
    void bar(const T& a) {
        static_assert(contains<T, As...>(), "Type does not exist");
        foo(a);
    }
};

int main()
{
    Foo<E, F, E, F> f;
    f.bar(F{});
    f.bar(E{});
    f.bar(1); // will hit static_assert
}

【讨论】:

    【解决方案2】:

    如果您实际上并不需要 bars 而只需要约束 foo - 我们可以使用 SFINAE 允许仅使用可转换为 As 之一的类型来调用它:

    template <class... As>
    class Foo {
    public:
        template <class T,
            class = std::enable_if_t<any<std::is_convertible<T, As>::value...>::value>>
        void foo(T const&) { ... }
    };
    

    我们可以在哪里实现 any 类似 bool_pack 技巧:

    template <bool... b> struct bool_pack  { };
    template <bool... b>
    using any = std::integral_constant<bool,
        !std::is_same<bool_pack<b..., false>, bool_pack<false, b...>>::value>;
    

    【讨论】:

    • enable_if_t 是 C++14 吗?
    • @user3612643 是的,但可以在 C++11 中实现。
    【解决方案3】:
    template <class CRTP, class A, class... As>
    struct FooBar
    {
        void bar(const A& a)
        {
            static_cast<CRTP*>(this)->foo(a);
        }
    };
    
    template <class CRTP, class A, class B, class... As>
    struct FooBar<CRTP, A, B, As...> : FooBar<CRTP, B, As...>
    {
        using FooBar<CRTP, B, As...>::bar;
    
        void bar(const A& a)
        {
            static_cast<CRTP*>(this)->foo(a);
        }
    };
    
    template <class... As>
    class Foo : FooBar<Foo<As...>, As...>
    {
        template <class, class, class...>
        friend struct FooBar;
    
    protected:
        template <class T>
        void foo(const T& a) { }
    
    public:
        using FooBar<Foo, As...>::bar;
    };
    

    DEMO

    【讨论】:

    • 很好,有没有办法摆脱“酒吧”之一?
    • 意思是“不”...我猜你需要一个不同于中间扩展点的终端扩展点?
    • @user3612643 无论using 指的是什么,它都必须存在。正如我所展示的,您可以拥有一个 bar 定义,但需要额外的模板机制。目前我想不出任何其他解决方案。未来将允许 using 进行包扩展
    【解决方案4】:
    template <class A, class... As>
    class Foo : public Foo<As...>
    {
    protected:
        using Foo<As...>::foo;
    public:
        using Foo<As...>::bar;
        void bar(const A& a) { foo(a); }
    };
    
    template <class A>
    class Foo<A>
    {
    protected:
        template <class T>
        void foo(const T& t) {  }
    public:
        void bar(const A& a) { foo(a); }
    };
    

    Piotr Skotnicki's answer 类似,它使用继承来为所有模板参数构建一个具有bar 重载的类。不过它更简洁一些,只有一个类模板和一个部分特化。

    【讨论】:

      【解决方案5】:
      template<class A, class Foo_t>
      class bar_t {
      public:
          void bar(const A &a) { Foo_t::foo(a); }
      };
      
      
      template<class ...As>
      class Foo : bar_t<As, Foo<As...> >... {
      protected:
          template<class T>
              void foo(const T& a) { /* do stuff */ }
      };
      

      【讨论】:

      • 除了 bar() 之外,这将声明多个 foo(),这不是所要求的。此外,由于继承,Foo<...>::bar() 默认只解析到 bar() 的最顶层实例。
      • @SamVarshavchik 感谢您的评论。但是,我不确定您所说的内容是如何发生的。据我所知,有一个函数模板“用于”它的许多实例。
      • @M.Aaggan 如果你在你的答案中解释你的代码也许会有所帮助。
      • 重载的函数必须在同一个命名空间中。这不适用于您的代码
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-08-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-05
      • 1970-01-01
      • 2016-12-01
      相关资源
      最近更新 更多