完美的转发技巧

Perfect forwarding trickery

如果您的对象有多个参数并且可以采用 l 或 r 值引用,那么构造函数的数量会迅速增加。一个小的模板技巧可以简化这种情况吗?或者这是否会导致我不知道的问题,或者我是否过度思考它并且有标准的方法来简化它?

// In the following context I using std::string as an example o
// something that can be moved or copied, where we would prefer
// a move rather than a copy.

// With just two parameters.
// I need 4 constructors to do the correct thing (copy/move) correctly.
class Original
{
    std::string   val1;
    std::string   val2;
    public:
        Original(std::string const& v1, std::string const& v2): val1(v1), val2(v2) {}
        Original(std::string const& v1, std::string&&      v2): val1(v1), val2(std::move(v2)) {}            
        Original(std::string&&      v1, std::string const& v2): val1(std::move(v1)), val2(v2) {}            
        Original(std::string&&      v1, std::string&&      v2): val1(std::move(v1)), val2(std::move(v2)) {}
};

如果参数类型是模板我可以使用完美转发。

// This is a check to see if a template is the specific class.
// Or can be trivially constructed from the parameters.
template<typename Actual>
using ValidString = std::enable_if_t<std::is_trivially_constructible_v<std::string, Actual>, bool>;


// With these I can simplify my requirement of 4 constructors
// and simplify to a single templaed constructor that can do perfect forwarding.
class Alternative
{
    std::string   val1;
    std::string   val2;
    public:
        template<typename V1, typename V2, ValidString<V1> = true, ValidString<V2> = true>
        Original(V1 v1, V2 v2): val1(std::forward<V1>(v1)), val2(std::forward<V2>(v2)) {}
};

两个问题:

  1. 是否已经有一种技术可以完美转发我错过的参数?
  2. 如果没有当前的技术,这里有什么问题?

在您的特定情况下执行此操作的最简单方法是:

class Original
{
    std::string val1;
    std::string val2;
public:
    Original(std::string v1, std::string v2) noexcept
      : val1{ std::move(v1) }
      , val2{ std::move(v2) }
    {}
};

这对于构造函数来说通常已经足够好了。它可能会复制+移动,而不仅仅是复制,但大多数类型移动起来都很便宜。您可能必须执行您的版本的一种情况是分配是有条件的。类似于:

template<typename T, typename Y>
void foo(T&& t, Y&& y)
{
   if (condition)
   {
      varT = std::forward<T>(t);
      varY = std::forward<Y>(y);
   }
}

复制+移动无法逃脱,因为您肯定不想要不必要的复制。

“如果您的对象有多个参数”

我认为,这在一定程度上取决于上下文和参数的数量。我经常遇到常见的参数化情况,在这种情况下,就设计和性能而言,引入具有简单直接(支撑)初始化的简单参数容器似乎是一个合适的解决方案。如果您希望在 ( [0..N] ) 上进一步构建完全灵活的参数数量,则此处的命名参数习语(组合)可能是一种强大的附加方法。但正如我所说,这取决于上下文和可能参数的最大数量,在大多数情况下不会仅将此应用于两个参数。然后,Ayxan Haqverdili 的答案是 Herb Sutter 推荐的答案,例如,我认为迄今为止最灵活、干净和非侵入性的方法。