51

The following code compiles fine:

g++ -std=c++11 test.cpp -Wall -Wextra -Wfatal-errors && ./a.out

However, if I remove the curly braces from {*this} and use *this instead, I will face with error:

error: use of deleted function ‘Obj::Position::Position(Obj::Position&&)’

What is the difference between {*this} and *this?

class Obj
{
    template<bool> friend class Position;

    double data;
public:
    class Position
    {
        const Obj& ref;
    public:
        inline Position(const Obj& ref): ref(ref){}
        inline Position(Position const &) = delete;
        inline Position(Position &&) = delete;
    };
    inline Obj(){}
    inline Obj(const double &data): data(data){}
    inline auto get_pos() const-> Position{return {*this};} /* <--- here */
    inline auto get_pos()-> Position{return {*this};}
};

int main()
{
    return 0;
}
ar2015
  • 5,558
  • 8
  • 53
  • 110

4 Answers4

38

When the curly braces are present, you're copy-list-initializing the return value, no copy/move constructor is involved. The return value is constructed in-place using the Position(const Obj&) constructor.

Note that the code would fail to compile even with the curly braces if you made the Position(const Obj&) constructor explicit because copy-list-initialization does not allow explicit constructors to be called.

If you omit the curly braces, then semantically a temporary Position object is constructed within the function, and the return value is move constructed from that temporary. In practice, most implementations will elide the move construction, but it still requires a viable move constructor to be present, which is not the case here because it has been explicitly deleted. This is the reason your code will not compile without braces.

Using a C++17 compiler, your code will compile even without the curly braces because of guaranteed copy-elision.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • I wonder why I see `Position(Obj::Position&&)’` in the error rather than `Position(Obj::Position&)’` when I omit braces. It looks like opposite. – ar2015 Aug 08 '17 at 00:39
  • @ar2015 Did you mean why do you see `Position(Position&&)` instead of `Position(Position const&)` in the error message? It's because you have a prvalue `Position` object from which to initialize the return value and the move constructor is a better match. If you comment out the `inline Position(Position &&) = delete;` line, the error message will contain the copy constructor. – Praetorian Aug 08 '17 at 00:42
  • it basically a case of "I prohibited it, then I tried to do that" If that's actually required – Swift - Friday Pie Aug 08 '17 at 07:52
19

The difference between the two is really quite subtle. C++11 introduced the feature list initialization (also sometimes called brace initialization):

Before C++11, when you want to default-construct and object o of type Obj and construct a Position p from o, you had to write

Obj o;              // default construct o
Obj::Position p(o); // construct p using Position(Obj const&)

A common error for beginners (especially with a Java background) was to try to write this:

Obj o();            // mistake: declares a function o returning an Obj
Obj::Position p(o); // error: no constructor takes a function 

The first line declares a function, and the second one tries to create a Position using a constructor that takes a function pointer as its argument. In order to have a uniform initializer syntax, C++11 introduced list initialization:

Obj oo{};             // new in C++11: default construct o of type Obj
Obj::Position p1(oo); // possible before (and after) C++11
Obj::Position p2{oo}; // new in C++11: construct p2 using Position(Obj const&)

This new syntax also works in return-statements, and this leads to the answer of your question: the difference between return {*this}; and return *this; is that the former initializes the return value directly from *this, whereas the latter first converts *this to a temporary Position object and then initializes the return value indirectly from this temporary, which fails because both the copy- and move-constructor have been explicitly deleted.

As the previous posters have noted, most compilers elide these temporary objects because they aren't really useful for anything; but this is only possible if they could be used in theory because either a copy or a move constructor is available. Because this leads to a lot of confusion (why do I need braces around my return statement? is the compiler going to elide the copy or not?), C++17 does away with these unnecessary temporaries, and initializes the return value directly in both cases (return {*this}; and return *this).

You can try this using a compiler that supports C++17. In clang 4.0 or gcc 7.1, you can pass --std=c++1z, and your code should compile fine with and without braces.

Tobias
  • 6,388
  • 4
  • 39
  • 64
14

This is a good one! This is because return {...} means "return an object of the function's return type initialized with list initializer ...".

List initializers are described in more details here:

http://en.cppreference.com/w/cpp/language/list%20initialization

So, the difference is that {*this} calls this:

inline Position(const Obj& ref): ref(ref){}

Whereas *this tries converting Obj& to Position by using explicitly deleted assignment operators (pre C++11, they would have to be made private, and you'd get an even more confusing error message if list initializers would be available...):

inline Position(Position const &) = delete;
inline Position(Position &&) = delete;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
isp-zax
  • 3,833
  • 13
  • 21
-2

frankly, using your class and the following main():

int main()
{
    Obj o1;
    cout<<"get position"<<'\n';
    Obj::Position pos= o1.get_pos();


    cout.flush();
    return 0;
}

it does not compile (gcc/mingw) in both cases(-std=c++14), with or without curly braces and it complains about missing Position(Position&&) constructor, which is deleted. It is reasonable because it seems that in both cases a construction of the temporary return object is performed, which then it is to be moved to the destination. Which is impossible as move constructor is deleted. Conversely, using -std=c++17 flag it compiles in both cases (with or without curly braces) as, most probably, we are hitting the guaranteed return-value optimization of c++17. Hope this helps.

Sandro
  • 37
  • 4