By Peter - Reinstate Monica


2016-01-27 15:35:10 8 Comments

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?

2 comments

@Igor Tandetnik 2016-01-27 15:44:17

[stmt.return]/2 ...A return statement with an expression of non-void type can be used only in functions returning a value; the value of the expression is returned to the caller of the function. The value of the expression is implicitly converted to the return type of the function in which it appears. A return statement can involve the construction and copy or move of a temporary object (12.2)... A return statement with a braced-init-list initializes the object or reference to be returned from the function by copy-list-initialization (8.5.4) from the specified initializer list.

[class.temporary]/1 Temporaries of class type are created in various contexts: ... returning a prvalue (6.6.3) ...

So yes, as far as I can tell, there's a semantic difference. typedBrace evaluates an expression S{}, which produces a prvalue of type S, 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) vs S s = S{}; (doesn't work), just obscured somewhat by a level of indirection.

@TartanLlama 2016-01-27 15:44:14

This is standard behaviour.

N4140 [stmt.return]/2: [...] A return statement with a braced-init-list initializes the object or reference to be returned from the function by copy-list-initialization (8.5.4) from the specified initializer list. [...]

This means that the initializations carried out by nakedBrace and typedBrace are equivalent to these:

S nakedBrace = {}; //calls default constructor
S typedBrace = S{}; //calls default, then copy constructor (likely elided)

Related Questions

Sponsored Content

4 Answered Questions

[SOLVED] What are copy elision and return value optimization?

1 Answered Questions

[SOLVED] Is copy/move elision allowed when returning *&object?

3 Answered Questions

[SOLVED] Copy ctor is called instead of move ctor - Can compiler give a warning?

  • 2018-02-20 12:27:56
  • AdyAdy
  • 143 View
  • 2 Score
  • 3 Answer
  • Tags:   c++

1 Answered Questions

1 Answered Questions

1 Answered Questions

[SOLVED] C++11 tuple with copy elision or move semantic

2 Answered Questions

2 Answered Questions

[SOLVED] Why is RVO disallowed when returning a parameter?

Sponsored Content