我希望创建一个围绕std :: make_pair的包装器,它接受一个参数并使用该参数来创建该对的第一个和第二个成员。此外,我希望利用移动语义。
天真地,我们可能会写(为了清晰起见忽略返回类型),
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));
}
但我有理由相信这是错误的。
所以,问题:
这有用吗?我怀疑不是因为如果使用右值引用调用foo(),则在构造通过值传递给Forward_or_duplicate()的T时,将调用T的移动构造函数(如果存在),从而破坏t。
即使它确实有效,它是最佳的吗?同样,我怀疑不会在从forward_or_duplicate()返回t时调用T的复制构造函数。
这似乎是一个常见问题。是否有惯用的解决方案?
答案 0 :(得分:5)
所以,问题:
- 这有用吗?我怀疑不是因为如果用rvalue引用调用foo()那么T的移动构造函数(如果存在)将是 在构造通过值传递的T时调用 forward_or_duplicate(),因此摧毁了。
醇>
不,t
中的foo
是一个左值,因此构造通过值传递的T
来自forward_or_duplicate()
的{{1}}调用复制构造函数。
- 即使它确实有效,它是否最佳?同样,我怀疑不会在返回t时调用T的复制构造函数 forward_or_duplicate()。
醇>
不,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;
不会复制。