19

In C++11 we can do in-class initialization using a "brace-or-equal-initializer" (words from the standard) like this:

struct Foo
{
  /*explicit*/ Foo(int) {}
};

struct Bar
{
  Foo foo = { 42 };
};

But if we un-comment explicit, it no longer compiles. GCC 4.7 and 4.9 say this:

error: converting to ‘Foo’ from initializer list would use explicit constructor ‘Foo::Foo(int)’

I found this surprising. Is it really the intention of the C++11 standard that this code doesn't compile?

Removing the = fixes it: Foo foo { 42 }; but I personally find this harder to explain to people who have been used to the form with = for decades, and since the standard refers to a "brace-or-equal-initializer" it's not obvious why the good old way doesn't work in this scenario.

John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • 2
    The `{}` initializer syntax is kind of a hack and it has a bunch of weird corner cases like this – M.M Oct 01 '14 at 08:38
  • I thought you had to use double braces - try it with `Foo foo = { { 42 } };` – Carl Burnett Oct 01 '14 at 08:48
  • @CarlBurnett: double braces seem to do the same thing as single braces--still broken. – John Zwinck Oct 01 '14 at 08:49
  • Does "brace-or-equal-initializer" mean that you have to use either "brace" or "equal" but not both? – anatolyg Oct 01 '14 at 08:53
  • 3
    @Matt McNabb: it's not so much `{}` being a hack as using `=` and hoping for elided construction that's a hack, but I do personally prefer it notationally. Anyway, given ***elision is an optimisation not a guarantee***, ignoring `explicit` means you risk an extra unintended construction. Seems that requiring code to either explicitly reflect that risk - `Foo foo = Foo{ 42 };` - is reasonable when the constructor's marked with `explicit`. The verbosity pushes people to consider and simplify. Tedious though. – Tony Delroy Oct 01 '14 at 08:56
  • Related: [What could go wrong if copy-list-initialization allowed explicit constructors?](https://stackoverflow.com/questions/9157041/what-could-go-wrong-if-copy-list-initialization-allowed-explicit-constructors) –  Oct 01 '14 at 09:07
  • 1
    @TonyD Agreed, but you may be wrong about the elision. [According to Herb Sutter](http://herbsutter.com/2013/05/09/gotw-1-solution/) the elision is in fact guaranteed. That said, I don’t know what he’s basing this assertion on. – Konrad Rudolph Oct 01 '14 at 09:18
  • Related reading on [explicit with multi-parameter constructors](http://stackoverflow.com/questions/4467142/why-is-explicit-allowed-for-default-constructors-and-constructors-with-2-or-more/4467658#4467658). – juanchopanza Oct 01 '14 at 09:22
  • @TonyD `= {}` has always been aggregate initialization, which follows its own rules. No temporaries involved. Nowadays, `= {}` is list-initialization, which follows its own rules, and still does not involve temporaries (here). – dyp Oct 01 '14 at 09:39
  • Konrad / dyp: solid point... `= { }` specifically is quite distinct from other `= ...` initialisation, and I hadn't realised elision was guaranteed for this specific case - nice! – Tony Delroy Oct 01 '14 at 10:00
  • @JohnZwinck One could guess that this has to do with consistency. The initialization via `=` and initializing function parameters is the same *copy-initialization*, and `void bar(Foo); bar({});` *implicitly* creates a `Foo`. – dyp Oct 01 '14 at 11:19
  • @KonradRudolph I just read over the appropriate sections of the standard: I believe Herb is wrong. `Foo a=x;` where `x` is not a `Foo` is equivalent to `Foo a=Foo(x);` where `Foo(x)` is restricted to non-`explicit` constructors(footnote 1). While elision is practically guaranteed, it is not actually guaranteed. The copy/move ctor need exist, and implementations are allowed to not elide. Footnote 1: Or, to be precise, `template T implicit( Arg&& arg) { return {std::forward(arg)}; }` then `Foo a=x;` is almost equivalent to `Foo a=implicit(x);`, except for lists.Gah – Yakk - Adam Nevraumont Oct 01 '14 at 14:04
  • @Yakk According to dyp's comment above that's wrong, and `={}` follows the special rules of aggregate initialisation. Unfortunately I cannot check the standard myself at the moment. – Konrad Rudolph Oct 01 '14 at 14:08
  • 1
    @KonradRudolph Hmm. Yep, 8.5 actually led me down a bad path: while it implies (to my reading) that `Foo x={a};` is copy-initialization, it is actually copy-list-initialization covered in 8.5.4. And copy-list-initialization is identical to direct-list-initialization except if an `explicit` constructor is chosen, it is blocked. Unlike `Foo x=a;` no temporary is created, logically or not. So [this works](http://ideone.com/3Ewhjx) -- blocked copy/move, `Foo a={x};` style, compiles. Without `{}` does not compile. – Yakk - Adam Nevraumont Oct 01 '14 at 14:25

3 Answers3

13

I can't explain the rationale behind this, but I can repeat the obvious.

I found this surprising. Is it really the intention of the C++11 standard that this code doesn't compile?

§13.3.1.7

In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed.


Removing the = fixes it: Foo foo { 42 }; but I personally find this harder to explain to people who have been used to the form with = for decades, and since the standard refers to a "brace-or-equal-initializer" it's not obvious why the good old way doesn't work in this scenario.

Foo foo { 42 } is direct initialization, whereas the equal sign (with braces) makes it copy-list-initialization. Another answer reasons that because compilation fails for copy-initialization (equal sign without braces), then it shouldn't be surprising that it also fails for copy-list-initialization, but the two fail for different reasons.

cppreference:

Direct-initialization is more permissive than copy-initialization: copy-initialization only considers non-explicit constructors and user-defined conversion functions, while direct-initialization considers all constructors and implicit conversion sequences.

And their page on the explicit specifier:

Specifies constructors and (since C++11) conversion operators that don't allow implicit conversions or copy-initialization.

On the other hand, for copy-list-initialization:

T object = {arg1, arg2, ...}; (10)

10) on the right-hand-side of the equals sign (similar to copy-initialization)

  • Otherwise, the constructors of T are considered, in two phases:

    • If the previous stage does not produce a match, all constructors of T participate in overload resolution against the set of arguments that consists of the elements of the braced-init-list, with the restriction that only non-narrowing conversions are allowed. If this stage produces an explicit constructor as the best match for a copy-list-initialization, compilation fails (note, in simple copy-initialization, explicit constructors are not considered at all)

As discussed in What could go wrong if copy-list-initialization allowed explicit constructors?, the compilation fails because the explicit constructor is selected but is not allowed to be used.

12

If Foo(int) is explicit, then this won't compile also:

Foo foo = 42;

So for "people who have been used to the form with = for decades" it won't be a surprise that the form with {} doesn't compile either.

Anton Savin
  • 40,838
  • 8
  • 54
  • 90
  • 5
    `Foo foo = 42` *implicitly* creates a `Foo` prvalue to initialize `foo` with. This is not the case for `Foo foo = {42}`, which does *not* implicitly create a `Foo`. – dyp Oct 01 '14 at 09:43
  • I'm with @dyp: I understand why `Foo foo = 42` won't work, and I'm OK with it. I am still bewildered as to why `Foo foo = { 42 }` doesn't work...it seems from another answer here that this may be what the standard intended or at least anticipated, but it seems...bad. – John Zwinck Oct 01 '14 at 11:17
7

widget w = {x};

This is called “copy list initialization.” It means the same as widget w{x}; except that explicit constructors cannot be used. It’s guaranteed that only a single constructor is called.

From http://herbsutter.com/2013/05/09/gotw-1-solution/

See the rest of the article for a more detailed discussion on the various ways you can initialise an object.

Community
  • 1
  • 1
Lionel
  • 326
  • 1
  • 4