C++ 将错误消息与命名的右值引用混淆

C++ 将错误消息与命名的右值引用混淆,c++,c++11,language-lawyer,rvalue-reference,pass-by-rvalue-reference,C++,C++11,Language Lawyer,Rvalue Reference,Pass By Rvalue Reference,考虑以下几点: struct my_type {}; my_type make_my_type() { return my_type{}; } void func(my_type&& arg) {} int main() { my_type&& ref = make_my_type(); func(ref); } 不用说,这段代码不会编译。我意识到,在第二个函数调用中,我需要使用 STD::MOVER()/CODE,但出于理解的目的,我想

考虑以下几点:

struct my_type {};

my_type make_my_type() { return my_type{}; }

void func(my_type&& arg) {}

int main()
{
    my_type&& ref = make_my_type();

    func(ref);
}
不用说,这段代码不会编译。我意识到,在第二个函数调用中,我需要使用<代码> STD::MOVER()/CODE,但出于理解的目的,我想考虑代码本身。

试图编译上述内容时,Clang 3.5告诉我:

错误:对“func”的调用没有匹配的函数

注意:候选函数不可行:第一个参数void func(my_type&&){

虽然g++4.9说了一些几乎相同的东西:

错误:无法将“我的类型”左值绑定到“我的类型&”

注意:正在初始化“void func(my_type&&)”的参数1

这些错误消息让我相当困惑,因为虽然
ref
肯定是一个左值,但它的类型仍然是
myu类型&
。。。不是吗

我试图准确地理解这里发生了什么,因此我想知道以下哪一项(如果有的话)是正确的:

  • 因为只有右值可以绑定到右值引用,并且
    ref
    是左值,所以它不能绑定到
    arg
    。Clang和g++发出的错误消息都具有误导性,它们声称
    ref
    是“无法转换”的(非引用)
    my_类型

  • 因为它是一个左值,
    ref
    在重载解析中被视为非引用的
    my\u类型
    ,尽管它的实际类型是
    my\u类型&
    。来自Clang和g++的错误消息具有误导性,因为它们显示的是内部用于函数匹配的类型,而不是真正的
    ref

  • main()
    的主体中,
    ref
    的类型是普通的
    myu-type
    ,尽管事实上我明确地写了
    myu-type&
    。因此,来自编译器的错误消息是准确的,而我的预期是错误的。然而,情况似乎并非如此,因为

      static_assert(std::is_same<decltype(ref), my_type&&>::value, "");
    
    static_断言(std::is_same::value,”);
    
    通行证

  • 还有一些我没有考虑过的魔法


重复一下,我知道解决方案是使用
std::move()
将rref转换回右值;我正在寻找“幕后”发生的事情的解释。

考虑以下三项任务:

my_type x = func_returning_my_type_byvalue();
my_type & y = func_returning_my_type_byvalue();
my_type && z = func_returning_my_type_byvalue();
第一个-您有一个局部变量
x
,它被初始化为函数调用的结果(右值),因此可以使用移动构造函数/赋值,或者完全省略x的构造(跳过,
x
在生成结果时由
func\u返回\u my\u type\u byvalue
就地构造)

请注意,
x
是一个左值-您可以获取它的地址,因此它本身也是一种引用类型。从技术上讲,所有非引用的变量都是对自身的引用。在这方面,左值是一个绑定站点,用于分配到已知存储持续时间内存并从中读取

第二个将不会编译-您不能将引用分配给结果(这种方式),您必须使用引用分配语法来别名现有的左值。但是,这样做很好:

my_type & y = func_returning_my_type_byreference();
// `y` will never use constructors or destructors
这就是为什么第三个存在的原因,当我们需要引用某个东西时,我们无法使用常规语法创建引用。在原始问题中类似于
func
的内容中,
arg
的生存期不是很明显。例如,如果没有明确的移动,我们无法做到这一点:

void func( my_type && arg ) {
    my_type && save_arg = arg;
}
不允许这样做的原因是,
arg
首先是对值的引用。如果存储
arg
的值(它所指的内容)如果要比
save_arg
的长度短,那么
save_arg
将调用该值的析构函数-实际上是捕获它。这里不是这样,
save_arg
将首先消失,因此在
func
之后,将一个左值转移到它中是没有意义的,我们仍然可以潜在地引用它

考虑一下,即使要使用
std:move
强制编译,析构函数仍不会在
func
中调用,因为您没有创建新对象,只是创建了一个新引用,然后在原始对象本身超出范围之前销毁了该引用

对于所有意图和目的,
arg
的行为就像它是
my\u类型&
,任何右值引用也是如此。诀窍是存储持续时间和通过引用传递延长生存期的语义。它都是引擎盖下的常规引用,没有“右值类型”

如果有帮助,请调用递增/递减运算符。存在两个重载,而不是两个运算符。
operator++(void)
(pre)和
operator++(int)
(post)。没有实际的
int
被传递,只是编译器对于不同的情况/上下文/关于值处理的协议有不同的签名。这在某种程度上是对引用的相同处理

如果右值和左值引用总是像左值一样被引用,那么有什么区别? 一句话:对象生命周期

必须始终将左值引用分配给使用存储持续时间较长的对象,即已构造的对象。这样,就不需要为左值引用变量的范围调用构造函数或析构函数,因为根据定义,我们得到了一个就绪对象,并在它被销毁之前将其忘记

对象以与定义相反的顺序隐式销毁也是相关的:

int a; // created first, destroyed last
int b; // created second, destroyed 2nd-last
int & c = b; // fine, `c` goes out of scope before `b` per above
int && d = std::move(a); // fine, `a` outlives `d`, same situation as `c`
如果我们分配给一个右值引用,这是一个左值引用,同样的规则也适用-根据定义,左值必须具有更长的存储空间,因此我们不需要为
c
甚至
dint a = 2, b = 3; // lvalues
int && temp = a + b; // temp is constructed in-place using the result of operator+(int,int)
func( std::move( variable ) ); // case 1
func( my_type() + my_type() ); // case 2