为什么不总是使用std::forward?

7
std::forwardstd::move 的区别是众所周知的,我们使用后者来保留转发对象的值类别,并使用前者强制转换为右值引用以启用移动语义。
在《effective modern C++》中,存在一个指南,它规定:
“在右值引用上使用 std::move,在通用引用上使用 std::forward。”
然而,在以下场景(以及不想改变值类别的场景)中,
template <class T>
void f(vector<T>&& a)
{
    some_func(std::move(a)); 
}

如果 a 不是一个转发引用而只是一个简单的右值引用,那么下面这样做不是完全相同吗?

template <class T>
void f(vector<T>&& a)
{
    some_func(std::forward<decltype(a)>(a)); 
}

由于这可以轻松地封装在类似于这样的宏中,

#define FWD(arg) std::forward<decltype(arg)>(arg)

总是使用这个宏定义会不会很方便?

void f(vector<T>&& a)
{
    some_func(FWD(a)); 
}

这两种写法完全等价,不是吗?

3
我认为使用宏解决方案比编写move()更方便这一观点存在问题,但我认为这是基于个人看法的问题。两种方法的效果相同。 - Barry
3
有时候参数是一个普通的引用,并且你确实想要移动向量。继续使用转发并不能实现这一点。 - David Haim
@Barry 我想确认它们是否等效,谢谢。我不同意它们是基于观点的,仅仅倡导一种编程风格并不意味着偏见,一个人可能有非常好的理由这样做(并且能够详细阐述)。 - Lorah Attkins
1
添加宏会使理解变得模糊。如果你正在故意做一些奇怪的事情,就像你在这里所做的那样,即使牺牲冗长,也应该是显而易见的 - tadman
1
关于“...这样做难道不是完全一样的吗...”我认为它确实是完全一样的;因为vector<T>&& a是一个右值引用,对右值引用进行无条件转换(移动)或有条件转换(转发)没有任何区别。 - Loreto
显示剩余3条评论
1个回答

8

嗯,有争议。在你的特定示例中它是等效的。但我不会形成习惯。一个原因是因为你想要转发和移动之间的语义区别。另一个原因是因为为了有一致的API,你还必须拥有MOV,这看起来真的很糟糕,并且没有任何作用。更重要的是,你的代码可能会意外失败。

考虑以下代码:

#include <iostream>

using namespace std;

#define FWD(arg) std::forward<decltype(arg)>(arg)

struct S {
    S() { cout << "S()\n"; }
    S(const S&) { cout << "S(const S&)\n"; }
    S(S&&) { cout << "S(S&&)\n"; }
};

void some_func(S) {}

void f(S&& s)
{
    some_func(FWD(s)); 
}

int main()
{
    f(S{});
}

这将打印出

S()
S(S&&)

然而,如果我只是改变FWD行,使其具有另一对(看似可选的)括号,像这样:

void f(S&& s)
{
    some_func(FWD((s))); 
}

现在我们得到了:

S()
S(const S&)

这是因为现在我们在一个表达式上使用了 decltype,它会评估为一个左值引用。

1
你提出了很好的观点。但是我不同意“括号似乎是可选的”这种表述。如果一个人正在使用decltype,应该知道这些内容,即使在std::forward<decltype...(这是通常在泛型lambda中遇到的语法)中也可能犯错,而不是宏定义的问题。 - Lorah Attkins
2
@Lorah 是的,但它是一个宏 - 你不能立即看到它使用了 decltype - Barry
1
@Barry 我猜如果它是一个函数,你也看不出它的作用,你总是需要阅读代码;我大多数时候理解与宏相关的“缺乏透明度”是指“奇怪的副作用或行为突变”,即使在阅读代码时也不明显(例如两次评估表达式等)。我并不排斥出于像FWD这样的原因使用宏,即文本替换(无双重评估、无偶然数据结构、无隐藏控制流)。你能否请看一下这个?(虽然不是主题,但我需要一些帮助) - Lorah Attkins
@LorahAttkins 这里的问题部分在于一开始使用了decltype。它并不是真正需要的,而且它还带来了许多微妙的问题。 - Yam Marcovic
@LorahAttkins 我同意你的观点。你提出的带有std::forward的宏确实有效(至少在之前的所有方法中都是如此)。@YamMarcovic所说的应该已经是众所周知的了。你可以将std::forward用于几乎所有情况,除非你特别需要一个rvalue转换,在这种情况下,你需要使用std::move - KeyC0de

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接