可变参数模板参数的元迭代

15
我希望能够概括以下模式:

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

有没有一种方法可以做到这一点?

1
可变参数序列,它是否可以包含相同类型的多个元素,还是序列中只能包含唯一类型的元素? - Nim
5个回答

12

另一种方法可能是在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
}

9

如果你实际上不需要 bar,而只需要限制 foo - 我们可以使用 SFINAE,仅允许将其用于转化为其中一个 A 类型的调用:

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&) { ... }
};

我们可以使用类似于bool_pack技巧的方式来实现any:

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
@user3612643 是的,但可以在C++11中实现。 - Barry

6
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


有没有办法去掉其中一个“bar”? - user3612643
意思是“没有”……我猜你需要一个与中间点不同的终端扩展点? - user3612643
@user3612643,无论using指的是什么,它必须存在。正如我所展示的,你可以通过额外的模板机制来实现单个bar定义。目前我想不到其他的解决方案。未来using将允许包扩展。 - Piotr Skotnicki

5
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的答案类似,这个方法使用继承来构建一个带有所有模板参数的bar重载的类。但是,它更加简洁,只有一个类模板和一个部分特化。


0
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 */ }
};

这将声明多个foo(),并且还会有bar(),这不是被要求的。此外,由于继承,Foo<...>::bar()默认情况下只会解析到最顶层的bar()实例。 - Sam Varshavchik
@SamVarshavchik 谢谢您的评论。然而,我不确定您所说的是如何发生的。据我所知,有一个函数模板 `foor' 和许多实例化。 - Mohammad Alaggan
1
@M.Alaggan 也许如果您在答案中加入代码的解释会更有帮助。 - TartanLlama
1
重载函数必须存在于同一命名空间中。但这并不适用于您的代码。 - Piotr Skotnicki

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接