Код выглядит следующим образом:
#include <iostream>
using namespace std;
class A {
};
A rtByValue() {
return A();
}
void passByRef(A &aRef) {
// do nothing
}
int main() {
A aa;
rtByValue() = aa; // compile without errors
passByRef(rtByValue()); // compile with error
return 0;
}
Компилятор g ++ выдает следующую ошибку:
d.cpp: In function ‘int main()’:
d.cpp:19:23: error: invalid initialization of non-const reference of type ‘A&’ from an rvalue of type ‘A’
d.cpp:12:6: error: in passing argument 1 of ‘void passByRef(A&)’
Это говорит о том, что я не могу передать rvalue в качестве аргумента неконстантной ссылки, но меня смущает то, почему я могу присвоить этому rvalue, как показывает код.
Передача Rvalue rtByValue()
для функции, которая ожидает, что ссылка на lvalue не работает, потому что это потребует инициализации аргумента ссылки на lvalue из rvalue. §8.5.3 / 5 описывает, как ссылки на lvalue могут быть инициализированы — я не буду цитировать это полностью, но это в основном говорит, что ссылка на lvalue может быть инициализирована
Поскольку аргумент, который нам нужно инициализировать, не является константной ссылкой, это не применимо.
С другой стороны,
rtByValue() = aa;
то есть присвоение временному объекту возможно из-за:
(§3.10 / 5) lvalue для объекта необходимо для того, чтобы модифицировать объект, за исключением того, что rvalue типа class может также использоваться для изменения его референта при определенных обстоятельствах. [Пример: функция-член, вызываемая для объекта (9.3), может модифицировать объект. — конец примера]
Так что это работает только потому, что A
имеет тип класса, и (неявно определенный) оператор присваивания является функцией-членом. (Увидеть этот связанный вопрос для дальнейших деталей.)
(Так что если rtByValue()
должны были вернуть, например, int
, тогда назначение не будет работать.)
Потому что вы можете (но не должны!) Переопределять оператор = так, что имеет смысл вызывать его для значения r. Рассмотрим следующий код:
#include<iostream>
using namespace std;
class foo;
foo* gotAssigned = NULL;
int assignedto = -1;
class foo {
public:
foo(int v) : val(v) {}
foo& operator=(int v) {
assignedto=v;
gotAssigned = this;
val = v;
return *this;
}
int val;
};
foo theFoo(2);
foo returnTheFooByValue() {
return theFoo;
}
main() {
returnTheFooByValue()=5;
cout << "[" << assignedto << "] " << theFoo.val << " versus " << gotAssigned->val << endl;
}
Теперь давайте скомпилируем его несколькими способами:
$ g++ -O0 -o rveq rveq.cc && ./rveq
[5] 2 versus 5
$ g++ -O1 -o rveq rveq.cc && ./rveq
[5] 2 versus 2
$ g++ -O4 -o rveq rveq.cc && ./rveq
[5] 2 versus -1218482176
Я не могу обещать, что вы увидите те же результаты.
Как видите, присваивание происходит, но любая попытка использовать объект, который получил назначенный результат, приводит к поведению, специфичному для реализации.
Кстати, это только применяется к пользовательским типам. Этот код:
int v(){
return 2;
}
main(){
v()=4;
}
не компилируется
@ddriver Это выводит число 7, как я и ожидал.
#include <iostream>
using namespace std;
class A {
public:
int i;
A() {i = 0x07;}
};
A rtByValue() {
return A();
}
void passByRef(A &aRef) {
cout << aRef.i;
}
int main() {
passByRef(rtByValue());
return 0;
}