Итак, отличия таки нашлись.
Ну, во-первых:
если у нас foo() внезапно начнет возвращать не переменную типа O, а таки O& (решили соптимизировать - возвращаем некую долгоживующую переменную, нет смысла создавать локальную копию её), то все внезапно поменяется:
#include <iostream>
using namespace std;
struct O {
int foo;
O(int i) : foo(i) {cout << "O(" << i <<")\n";}
O(const O& r) {foo=r.foo; cout << "Copy constructor\n";}
~O(){cout << "~O(" << foo << ")\n";}
O& operator=(const O& r) {cout << "=\n"; return *this;}
};
O o(0);
O& foo() {
return o;
}
int main() {
cout << "=== begin ===\n";
{const O& o0 = foo();}
cout << "--- mmMmm ---\n";
{const O o1 = foo();}
cout << "=== end ===\n";
return 0;
}
Программа напишет теперь такое:
O(0)
=== begin ===
--- mmMmm ---
Copy constructor
~O(0)
=== end ===
~O(0)
То есть в случае const O o1 = foo(); будет создавать копию переменной (вызывая для этого копирующий конструктор), а const O& = foo(); как было оптимальным так и останется. Никто ничего не заметит.
http://liveworkspace.org/code/1vbybT$1
Это первое семантическое отличие. А вот второе:
struct Foo
{
Foo(Foo &&)=delete;
Foo(int,int) {}
};
Foo create() { return {12,42}; }
int main()
{
const Foo &t1 = create(); // OK
const Foo t2 = create(); // error
}
Во втором случае (t2) требуется от типа Foo наличие move semantic (семантики перемещения).
http://liveworkspace.org/code/3ylY4h$21
Все это отрылось по результатам обсуждения на rsdn:
http://rsdn.ru/forum/cpp/5104989.flat.1