通用参考参数使用两次

时间:2015-08-11 22:55:55

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

我希望创建一个围绕std :: make_pair的包装器,它接受一个参数并使用该参数来创建该对的第一个和第二个成员。此外,我希望利用移动语义。

天真地,我们可能会写(为了清晰起见忽略返回类型),

template <typename T>
void foo(T&& t)
{
  std::make_pair(std::forward<T>(t),
                 std::forward<T>(t));
}

但这不太可能达到我们想要的效果。

我们想要的是:

  • 在使用(const)左值引用参数调用foo的情况下,我们应该将这个(const)引用传递给std :: make_pair,并修改两个参数。
  • 在使用rvalue引用参数调用foo的情况下,我们应该复制引用的对象,然后使用原始rvalue引用调用std :: make_pair以及对新创建的对象的rvalue引用。

到目前为止,我想出的是:

template <typename T>
T forward_or_duplicate(T t)
{
  return t;
}

template <typename T>
void foo(T&& t)
{
  std::make_pair(std::forward<T>(t),
                 forward_or_duplicate<T>(t));
}

但我有理由相信这是错误的。

所以,问题:

  1. 这有用吗?我怀疑不是因为如果使用右值引用调用foo(),则在构造通过值传递给Forward_or_duplicate()的T时,将调用T的移动构造函数(如果存在),从而破坏t。

  2. 即使它确实有效,它是最佳的吗?同样,我怀疑不会在从forward_or_duplicate()返回t时调用T的复制构造函数。

  3. 这似乎是一个常见问题。是否有惯用的解决方案?

2 个答案:

答案 0 :(得分:5)

  

所以,问题:

     
      
  1. 这有用吗?我怀疑不是因为如果用rvalue引用调用foo()那么T的移动构造函数(如果存在)将是   在构造通过值传递的T时调用   forward_or_duplicate(),因此摧毁了。
  2.   

不,t中的foo是一个左值,因此构造通过值传递的T 来自forward_or_duplicate()的{​​{1}}调用复制构造函数。

  
      
  1. 即使它确实有效,它是否最佳?同样,我怀疑不会在返回t时调用T的复制构造函数   forward_or_duplicate()。
  2.   

不,t是一个函数参数,因此返回隐式移动,不会复制。

尽管如此,这个版本将更有效,更安全:

t

如果template <typename T> T forward_or_duplicate(std::remove_reference_t<T>& t) { return t; } 是左值参考,则会产生与之前相同的签名。如果T不是参考,那么这可以为您节省一笔费用。此外,它将T放入非推导的上下文中,这样您就不会忘记指定它。

答案 1 :(得分:0)

您的确切代码有效。它的轻微变化(即,不调用make_pair但是其他一些函数)导致未指定的结果。即使它看起来有效,但远离这行代码(本地正确的)的细微变化可能会破坏它。

您的解决方案不是最佳选择,因为它可以复制T两次,即使它有效,只需要复制一次。

这是迄今为止最简单的解决方案。它没有修复由其他地方更改的代码引起的微妙中断,但是如果你真的在调用make_pair而不是一个问题:

template <typename T>
void foo(T&& t) {
  std::make_pair(std::forward<T>(t),
             static_cast<T>(t));
}

static_cast<T>(t)如果T&&是左值,则推导类型T&&是noop,如果T&&是右值则是副本。

当然,也可以使用static_cast<T&&>(t)代替std::forward<T>(t),但人们也不会这样做。

我经常这样做:

template <typename T>
void foo(T&& t) {
  T t2 = t;
  std::make_pair(std::forward<T>(t),
             std::forward<T>(t2));
}

但这阻碍了理论上的省略机会(这里不会发生)。

通常,在与std::forward<T>(t)相同的函数调用上调用static_cast<T>(t)或任何等效的复制或转发函数是个坏主意。未指定参数的计算顺序,因此如果使用std::forward<T>(t)的参数不是T&&类型,并且其构造函数看到rvalue T并将状态移出其中,则static_cast<T>(t)在状态t被删除后可以评估

这不会发生在这里:

template <typename T>
void foo(T&& t) {
  T t2 = t;
  std::make_pair(std::forward<T>(t),
             std::forward<T>(t2));
}

因为我们将复制或转发移到另一行,我们在其中初始化t2

T t2=t;似乎总是复制,如果T&&是左值参考,T也是左值参考,而int& t2 = t;不会复制。