完美转发 class 可变参数

Perfect forwarding class variadic parameters

我有一个带有可变类型参数的 class。在 class 里面,我有一个方法接受这些类型的参数,将它们组成一个元组并将它们存储在一个向量中。我想要的是使用完美转发来避免不必要的复制。我通过在方法前面加上另一个可变参数模板来解决它,我转发这些新类型而不是旧类型,但我想知道是否有更好的方法。

让我向您展示我的代码示例:

template<typename ... Tlist>
class A{
public:
    template<typename ... Xlist>
    void method(Xlist && ... plist){
        // some code 
        std::vector<std::tuple<Tlist...>> vec;
        vec.push_back(std::make_tuple(std::forward<Xlist>(plist)...));
        // some other code
    }

};

这适用于正确的类型,并且它不会编译不正确的类型,所以我想它没问题。但我想要的是以某种方式在方法 header 中使用 Tlist 类型,像这样:

template<typename ... Tlist>
class A{
public:
    void method(Tlist && ... plist){
        // some code 
        std::vector<std::tuple<Tlist...>> vec;
        vec.push_back(std::make_tuple(std::forward<Tlist>(plist)...));
        // some other code
    }

};

但这只适用于右值。

那么有没有办法避免使用另一个模板,同时仍然可以完美转发?

解决这个问题最简单的方法就是简单地取一包值,然后从中 move:

template<class...Ts>
struct A{
  void method(Ts...ts){
    // some code 
    std::vector<std::tuple<Ts...>> vec;
    vec.emplace_back(std::forward_as_tuple(std::move(ts)...));
    // some other code
  }
};

如果 Ts 包含引用,则上面的代码表现不佳,但您的原始代码也没有。它还会强制使用冗余 move,这对于某些类型来说是昂贵的。最后,如果您没有支持 vec,它会强制您的类型 可移动 -- 下面的解决方案没有。

这是迄今为止对您的问题最简单的解决方案,但实际上并不完美。


这是一个更复杂的解决方案。我们从一些元编程开始。

types 是一组类型:

template<class...>struct types{using type=types;};

conditional_t 是一个 C++14 别名模板,使其他代码更简洁:

// not needed in C++14, use `std::conditional_t`
template<bool b, class lhs, class rhs>
using conditional_t = typename std::conditional<b,lhs,rhs>::type;

zip_test 采用一个测试模板和两个类型列表。它依次测试 lhs 的每个元素与 rhs 的相应元素。如果全部通过,则为true_type,否则为false_type。如果列表长度不匹配,则无法编译:

template<template<class...>class test, class lhs, class rhs>
struct zip_test; // fail to compile, instead of returning false

template<
  template<class...>class test,
  class L0, class...lhs,
  class R0, class...rhs
>
struct zip_test<test, types<L0,lhs...>, types<R0,rhs...>> :
  conditional_t<
    test<L0,R0>{},
    zip_test<test, types<lhs...>, types<rhs...>>,
    std::false_type
  >
{};

template<template<class...>class test>
struct zip_test<test, types<>, types<>> :
  std::true_type
{};

现在我们在您的 class 上使用它:

// also not needed in C++14:
template<bool b, class T=void>
using enable_if_t=typename std::enable_if<b,T>::type;
template<class T>
using decay_t=typename std::decay<T>::type;

template<class...Ts>
struct A{
  template<class...Xs>
  enable_if_t<zip_test<
    std::is_same,
    types< decay_t<Xs>... >,
    types< Ts... >
  >{}> method(Xs&&... plist){
    // some code 
    std::vector<std::tuple<Tlist...>> vec;
    vec.emplace_back(
      std::forward_as_tuple(std::forward<Xlist>(plist)...)
    );
    // some other code
  }
};

Xs 限制为与 Ts 完全相同。现在我们可能想要一些稍微不同的东西:

  template<class...Xs>
  enable_if_t<zip_test<
    std::is_convertible,
    types< Xs&&... >,
    types< Ts... >
  >{}> method(Xs&&... plist){

我们在这里测试传入的参数是否可以转换为存储的数据。

我又改了forward_as_tuple代替了make_tupleemplace代替了push,这两个都是为了让完美转发一路走下去下来。

对于上述代码中的任何拼写错误,我们深表歉意。

请注意,在 C++1z 中,我们可以不使用 zip_test,而是使用折叠表达式直接扩展 enable_if 中的测试。

也许我们可以在 C++11 中使用 std::all_ofconstexpr initializer_list<bool> 做同样的事情,但我还没有尝试过。

zip 在此上下文中指的是压缩到相同长度的列表,因此我们按照从一个到另一个的顺序对元素进行配对。

此设计的一个显着缺点是它不支持匿名 {} 参数构造,而第一个设计支持。还有其他问题,都是完美转发的常见故障。

正如 Yakk 在他的回答中所说,有 "easy" 方式和 "perfect forwarding" 方式来做到这一点。它们并不相同,但我认为他通过关注 forward_as_tupletuple 的转换构造函数使情况过于复杂。即 (DEMO):

template<typename ... Ts>
class A {
public:
  // "Simple" method. Does not perfect forward.
  // Caller's expressions are copied/moved/converted at the callsite
  // into Ts, and then moved (for non-reference types) or copied
  // (reference types) into the vector.
  void simple_method(Ts... ts) {
    // some code 
    std::vector<std::tuple<Ts...>> vec;
    vec.emplace_back(std::forward<Ts>(ts)...);
    // some other code
  }

  // "Perfect forwarding" method.
  // Caller's expressions are perfectly forwarded into the vector via
  // emplace.
  template <typename...Us>
  void perfect_forwarding_method(Us&&...us) {
    // some code 
    std::vector<std::tuple<Ts...>> vec;
    vec.emplace_back(std::forward<Us>(us)...);
    // some other code
  }

  // Constraint alias.
  template <typename...Us>
  using Constructible = typename std::enable_if<
    std::is_constructible<std::tuple<Ts...>, Us...>::value
  >::type;

  // "Constrained Perfect forwarding" method.
  // Caller's expressions are perfectly forwarded into the vector via
  // emplace. Substitution failure if tuple<Ts...> cannot be constructed
  // from std::forward<Us>(us)...
  template <typename...Us, typename = Constructible<Us...>>
  void constrained_perfect_forwarding_method(Us&&...us) {
    // some code 
    std::vector<std::tuple<Ts...>> vec;
    vec.emplace_back(std::forward<Us>(us)...);
    // some other code
  }
};

所有三种方法都能正确处理 Ts 中的 lvalue/rvalue 引用类型。