通用引用参数使用了两次

Universal reference argument used twice

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

天真地,我们可以写(为清楚起见忽略 return 类型),

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

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

我们要的是:

到目前为止我想出的是:

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. 即使它有效,它是最优的吗?同样,我怀疑当 returning t from forward_or_duplicate().

  3. 时不会调用 T 的复制构造函数
  4. 这似乎是一个常见问题。有惯用的解决方案吗?

So, questions:

  1. Does this work? I suspect not in that if foo() is called with an rvalue reference then T's move constructor (if it exists) will be called when constructing the T passed by value to forward_or_duplicate(), thus destroying t.

不是,foo中的t是左值,所以构造T传值给 forward_or_duplicate()t 调用复制构造函数。

  1. Even if it does work, is it optimal? Again, I suspect not in that T's copy constructor will be called when returning t from forward_or_duplicate().

不,t 是一个函数参数,所以 return 隐式移动,而不是复制。

也就是说,这个版本会更高效、更安全:

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));
}

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));
}

但这阻止了理论上的省略机会(此处不会发生)。

一般来说,在与 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;不会复制。