Consider the following program:
#include<iostream>
using namespace std;
struct S
{
S() = default;
S(const S& other) = delete;
S(S&& other) = delete;
int i;
};
S nakedBrace()
{
return {}; // no S constructed here?
}
S typedBrace()
{
return S{};
}
int main()
{
// produce an observable effect.
cout << nakedBrace().i << endl; // ok
cout << typedBrace().i << endl; // error: deleted move ctor
}
Sample session:
$ g++ -Wall -std=c++14 -o no-copy-ctor no-copy-ctor.cpp
no-copy-ctor.cpp: In function 'S typedBrace()':
no-copy-ctor.cpp:19:12: error: use of deleted function 'S::S(S&&)'
return S{};
^
no-copy-ctor.cpp:8:5: note: declared here
S(S&& other) = delete;
It astonishes me that gcc accepts nakedBrace()
. I thought that conceptually the two functions are equivalent: A temporary S
is constructed and returned. Copy elision may or may not be performed, but the move or copy ctor (both are deleted here) must still be accessible, as mandated by the standard (12.8/32).
Does that mean that nakedBrace()
never constructs an S? Or it does, but directly in the return value with brace-initialization, so that no copy move/ctor is conceptually required?
This is standard behaviour.
This means that the initializations carried out by
nakedBrace
andtypedBrace
are equivalent to these:So yes, as far as I can tell, there's a semantic difference.
typedBrace
evaluates an expressionS{}
, which produces a prvalue of typeS
, then attempts to copy-construct its return value from that expression.nakedBrace
instead constructs its return value straight from braced-init-list.It's the same situation as with
S s{};
(works) vsS s = S{};
(doesn't work), just obscured somewhat by a level of indirection.