完美的转发类可变参数

时间:2015-04-06 15:52:31

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

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

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

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
    }

};

这适用于正确的类型,并且无论如何都不会使用不正确的类型编译,所以我猜它没关系。但我想要的是以某种方式使用方法头中的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
    }

};

但这只适用于右值。

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

2 个答案:

答案 0 :(得分:3)

解决问题的最简单方法就是获取一组值,并从中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
{};

现在我们在你的课上使用它:

// 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内直接扩展测试。

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

在此上下文中,

zip指的是压缩到相同长度的列表,因此我们按顺序将元素从一个配对到另一个。

这个设计的一个重要缺点是它不支持匿名{}构造参数,而第一个设计确实如此。还有其他问题,这是完美转发的常见失败。

答案 1 :(得分:2)

正如Yakk在他的回答中所说的那样,有“简单”的方式和“完美的转发”方式来做到这一点。它们并不相同,但我认为他通过固定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中的左值/右值引用类型。