通用引用参数使用了两次

Universal reference argument used twice

本文关键字:两次 引用 参数      更新时间:2023-10-16

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

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

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

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

我们想要的是:

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

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

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的move构造函数(如果存在(,从而破坏T。

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

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

所以,问题:

  1. 这行得通吗?我怀疑不是因为如果用右值引用调用foo((,那么T的move构造函数(如果存在的话(将是在构造通过值传递给的T时调用forward_or_duplicate((,从而销毁t

否,foo中的t是一个左值,因此构造由值传递给的T来自tforward_or_duplicate()调用复制构造函数。

  1. 即使它确实有效,它是最优的吗?同样,我怀疑在从forward_or_duplicate((

不,t是一个函数参数,所以返回隐含地移动,而不是复制。

也就是说,这个版本将更加高效和安全:

template <typename T>
T forward_or_duplicate(std::remove_reference_t<T>& t)
{
  return t;
}

如果T是左值引用,则会产生与以前相同的签名。如果T不是参考,这将为您节省一次移动。此外,它将T放入一个非推导上下文中,这样您就不会忘记指定它

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

您的解决方案不是最优的,因为它可以复制T两次,即使它工作,也只需要复制一次。


这是迄今为止最简单的解决方案。它不能修复其他地方的代码更改引起的细微中断,但如果您真的在调用make_pair,那就不成问题了:

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

对于推导类型T&&,如果T&&是左值,则static_cast<T>(t)是noop;如果T&&是右值,则为copy。

当然,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));
}

但这阻碍了一个理论省略的机会(这里没有发生(。

通常,在与static_cast<T>(t)相同的函数调用上调用std::forward<T>(t)或任何等效的复制或转发函数是个坏主意。未指定参数的求值顺序,因此,如果使用std::forward<T>(t)的参数不是T&&类型,并且其构造函数看到右值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;不复制。