通用引用参数使用了两次
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));
}
但这不太可能达到我们想要的效果。
我们要的是:
- 在使用 (const) 左值引用参数调用 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));
}
但我有理由相信这是错误的。
所以,问题:
这个有用吗?我怀疑不是因为如果使用右值引用调用 foo(),那么在构造按值传递给 forward_or_duplicate() 的 T 时将调用 T 的移动构造函数(如果它存在),从而破坏 t.
即使它有效,它是最优的吗?同样,我怀疑当 returning t from forward_or_duplicate().
时不会调用 T 的复制构造函数
这似乎是一个常见问题。有惯用的解决方案吗?
So, questions:
- 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
调用复制构造函数。
- 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;
不会复制。
我希望围绕 std::make_pair 创建一个包装器,它接受一个参数并使用该参数构成该对的第一个和第二个成员。此外,我希望利用移动语义。
天真地,我们可以写(为清楚起见忽略 return 类型),
template <typename T>
void foo(T&& t)
{
std::make_pair(std::forward<T>(t),
std::forward<T>(t));
}
但这不太可能达到我们想要的效果。
我们要的是:
- 在使用 (const) 左值引用参数调用 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));
}
但我有理由相信这是错误的。
所以,问题:
这个有用吗?我怀疑不是因为如果使用右值引用调用 foo(),那么在构造按值传递给 forward_or_duplicate() 的 T 时将调用 T 的移动构造函数(如果它存在),从而破坏 t.
即使它有效,它是最优的吗?同样,我怀疑当 returning t from forward_or_duplicate().
时不会调用 T 的复制构造函数
这似乎是一个常见问题。有惯用的解决方案吗?
So, questions:
- 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
调用复制构造函数。
- 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;
不会复制。