【问题标题】:How do I pass on a variadic argument list while keeping a single argument如何在保留单个参数的同时传递可变参数列表
【发布时间】:2015-04-02 04:04:07
【问题描述】:

假设我有一个基类,以后可能会通过派生它来“扩展”,让我们称这个类为Base,扩展名为Derived类的模板签名是固定的,不能更改(即,我们不能更改类的模板参数)。 Derived 类的编写者对 Base 一无所知,只是它可能为其构造函数接受一些参数。

但是,最终派生类的调用者知道应该传递多少个参数。我该如何写这个Derived 扩展?这是我所拥有的:

struct Base
{
    Base(int baseArg) {}
};

struct Derived : public Base
{
    template <typename... Args>
    Derived(Args&&... args, int derivedArg)
    :
        Base(std::forward<Args>(args)...)
    {
    }
};

当我尝试使用Derived d(1, 1); run this 时,我收到以下错误消息:

prog.cpp: In function 'int main()':
prog.cpp:19:16: error: no matching function for call to 'Derived::Derived(int, int)'
  Derived d(1, 1);
                ^
prog.cpp:19:16: note: candidates are:
prog.cpp:11:2: note: template<class ... Args> Derived::Derived(Args&& ..., int)
  Derived(Args&&... args, int myArg)
  ^
prog.cpp:11:2: note:   template argument deduction/substitution failed:
prog.cpp:19:16: note:   candidate expects 1 argument, 2 provided
  Derived d(1, 1);
                ^
prog.cpp:8:8: note: constexpr Derived::Derived(const Derived&)
 struct Derived : public Base
        ^
prog.cpp:8:8: note:   candidate expects 1 argument, 2 provided
prog.cpp:8:8: note: constexpr Derived::Derived(Derived&&)
prog.cpp:8:8: note:   candidate expects 1 argument, 2 provided

Derived 的构造函数应该有 2 个参数,使用第一个参数来构造自身并将第二个参数传递给基类。为什么这不起作用?

【问题讨论】:

  • 将单个参数放在函数签名的首位

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


【解决方案1】:

nth获取部分参数的第n个元素:

template<size_t n, class...Args>
auto nth( Args&&... args )
->typename std::tuple_element<n,std::tuple<Args&&...>>::type
{
  return std::get<n>( std::forward_as_tuple(std::forward<Args>(args)...) );
}

这使用上面的内容来提取最后一个也是最后一个参数并将它们发送到适当的位置:

struct Derived : public Base {
  struct tag{};
  template <typename... Args>
  Derived(Args&&... args) : Derived(
    tag{},
    std::make_index_sequence<sizeof...(Args)-1>{},
    std::forward<Args>(args)...
  ){}
  template<size_t...Is, class...Args>
  Derived(tag, std::index_sequence<Is...>, Args&&...args ):
    Base(nth<Is>(std::forward<Args>(args)...)...)
  {
    int derivedArg = nth<sizeof...(Args)-1>(std::forward<Args>(args)...);
  }
};

我们为前 n-1 个元素构建一个序列,将它们传递给 base,并为我们自己存储最后一个元素。

不过,如果你把额外的参数放在前面,那就容易多了。

【讨论】:

  • nth 的 C++14 版本应该使用 decltype(auto),不是吗?
  • @t.c.是的:但是使用元组元素制作了 C++11 时期。
  • tag 是干什么用的? (在第一个 std::forward 之后还需要一个 ...
  • @0x499602D2 Derived 有一个完美的转发ctor。 tag 是一种不在参数包中的类型,因此我可以转发给行为不同的第二个 ctor。索引序列是不够的,因为Base 可能有一个将索引序列作为第一个参数的 ctor。
  • std::get&lt;n&gt;( std::forward_as_tuple(std::forward&lt;Args&gt;(args)...) ); - 天才!我正在使用它来拦截转发的参数并在派生类中对&lt;0&gt; 做一些不同的事情(在转发到基类之后)。巫术!
【解决方案2】:

Derived 构造函数是非推导上下文。来自 [temp.deduct.type],§14.8.2.5/5:

未推断的上下文是:

——[..]

——一个函数参数包,不出现在parameter-declaration-list的末尾。

在您的情况下,参数包不在末尾 - 在它之后还有一个参数,因此它是一个非推导上下文,这会使您的程序格式错误。简单的解决方案是颠倒排序:

template <typename... Args>
Derived(int derivedArg, Args&&... args) // totally OK
:
    Base(std::forward<Args>(args)...)
{
}

【讨论】:

  • 太好了,我非常感谢标准参考 :)
猜你喜欢
  • 1970-01-01
  • 2016-10-02
  • 1970-01-01
  • 2011-04-01
  • 1970-01-01
相关资源
最近更新 更多