7

I understand that when objects are returned by-value from a function, their copy-constructors are called. If a class has a deleted copy-constructor, returning by value will fail.

struct X {
    X(const X &) = delete;
};

X f() {
   return X{};
}

error: call to deleted constructor of 'X'

C++11 gives us extended-initializers. And I read somewhere on a SO post that this

X f() {
    return {};
}

is the same as

X f() {
    return X{};
}

So why doesn't the below code give me an error? It passes and I even get to call the function in main:

struct D {
   D(const D &) = delete;
};

D f() { return {}; }

int main()
{
   f();
}

Here is a demo. No error is reported. I find that weird because I believe that the copy-constructor should be called. Can anyone explain why no error is given?

template boy
  • 10,230
  • 8
  • 61
  • 97
  • What happens if you delete the move constructor? C++11 states that it will be used if available. – Edward Strange Mar 10 '13 at 22:14
  • Interestingly, I cannot get the code to compile on gcc 4.7.2 or a 4.8 snapshot. [demo](http://ideone.com/L94cwe). – juanchopanza Mar 10 '13 at 22:15
  • @CrazyEddie When I delete it the error is given. Wow, that was unexpected. Mind posting that as an answer? – template boy Mar 10 '13 at 22:17
  • http://liveworkspace.org/code/4yc7Hy$2200 is a gcc 4.7.2 version of the same code. – Yakk - Adam Nevraumont Mar 10 '13 at 22:17
  • Wouldn't feel comfortable giving it as an answer. I'm not entirely sure why it was possible to call the move there and not in the first. – Edward Strange Mar 10 '13 at 22:26
  • possible duplicate of [Can we return objects having a deleted/private copy/move constructor by value from a function?](http://stackoverflow.com/questions/7935639/can-we-return-objects-having-a-deleted-private-copy-move-constructor-by-value-fr) – ildjarn Mar 12 '13 at 19:37

1 Answers1

12

And I read somewhere on a SO post that this [...] is the same as [...]

They were wrong. They're similar, but not the same.

By using the braced-init-list, you are able to initialize the return value in-place. If you create a temporary, then what you're doing is creating the temporary and then copying it into the return value. Any compiler worth its salt will elide it, but the copy constructor still must be accessible.

But since the braced-init-list initializes the return value in-place, you don't need access to the copy constructor.

From the standard, section 6.6.3, p2:

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.

Note that "copy-list-initialization" is not similar to "copy-initialization"; it doesn't do any copying and therefore it doesn't require an accessible copy constructor. The only difference between "copy-list-initialization" and "direct-list-initialization" is that the former will choke on explicit constructors.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • So you're saying that, if you used a braced-init-list in a return statement, it basically forces copy/move elision? – Seth Carnegie Mar 10 '13 at 22:25
  • 1
    @SethCarnegie: No. I'm saying that if you use a braced-init-list, there is *no copying*. Copy elision still requires an accessible copy constructor; braced-init-lists initialize the return value in-place, without a copy/move operation. – Nicol Bolas Mar 10 '13 at 22:25
  • Oh, this is interesting, didn't know that. Could you quote the part of the Standard that specifies that? – Andy Prowl Mar 10 '13 at 22:25
  • @NicolBolas that's why I said "basically", I know it's not really elision. And yes, I was just about to ask for a standard reference too, for when someone asks me. – Seth Carnegie Mar 10 '13 at 22:26
  • @NicolBolas: Hm, but that quote just says that the object to be returned is list-initialized, not that the assigned object is direct-initizialized from the initialization list. Or am I missing something? – Andy Prowl Mar 10 '13 at 22:30
  • Interestingly, if you go to the OP's live workspace you can try the code in gcc 4.8. It vomits on the use of brace initializer claiming it can't be converted to D. – Edward Strange Mar 10 '13 at 22:31
  • @CrazyEddie: Honestly, that's what I would expect – Andy Prowl Mar 10 '13 at 22:31
  • @AndyProwl: I didn't say it was "direct-initialized". I was very careful in *not* using the word "direct" (because I *knew* people would take it the wrong way); I said it was "in-place". – Nicol Bolas Mar 10 '13 at 22:32
  • @CrazyEddie: GCC 4.8 has a bug in it, then. – Nicol Bolas Mar 10 '13 at 22:32
  • @SethCarnegie: ICC 13.0.1 also accepts it, but I still haven't understood why. – Andy Prowl Mar 10 '13 at 22:32
  • @AndyProwl: It accepts it because of exactly what I said. Uniform initialization never *copies anything*. It never requires an accessible copy constructor to do its initialization. – Nicol Bolas Mar 10 '13 at 22:34
  • Correct me if I'm wrong, but a braced init list is not the same as uniform initialization. An object actually has to have an init list constructor to be built from the former. The latter just changes '()' to '{}'. The standard part being quoted seems to be talking about the former and I would expect that to result in a conversion error. – Edward Strange Mar 10 '13 at 22:37
  • @AndyProwl so the `return {...}` initialises the return value; if you then do `D d = f()` you'll get an error. – Seth Carnegie Mar 10 '13 at 22:37
  • @NicolBolas: I think I somehow read a `D d = ` when there was none. The return value of `f()` is neglected. So I think now I understand why no error is emitted. Thank you. – Andy Prowl Mar 10 '13 at 22:37
  • @CrazyEddie no; read 8.5.4/3 and you'll see all the types of initialisation it does, specifically `If the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.`. – Seth Carnegie Mar 10 '13 at 22:38
  • [Copy initialization error forced](http://liveworkspace.org/code/4yc7Hy$2270) example. " List-initialization can occur in direct-initialization or copy- initialization contexts; list-initialization in a direct-initialization context is called direct-list-initialization and list-initialization in a copy-initialization context is called copy-list-initialization." -- so yes, `return {...}` directly calls non-explicit constructors on the return value. Neat! This is similar to how you can pass a no-copy no-move `X` by value to a function: via `{...}` initialization. – Yakk - Adam Nevraumont Mar 10 '13 at 22:39
  • @CrazyEddie: "*braced init list is not the same as uniform initialization*" Uniform initialization, as coined by Stroustrup, is the term for using braced-init-lists to initialize any value. The concept became uniform initialization when he proposed to extend `{}` syntax to initialize anything, whether with constructors directly or with `initializer_list`s. – Nicol Bolas Mar 11 '13 at 00:15