15

Executables produced by clang 3.5.0 and gcc 4.9.1 from the code

#include <iostream>

struct Foo
{
   Foo() { std::cout << "Foo()" << std::endl; }
   Foo(int x) { std::cout << "Foo(int = " << x << ")" << std::endl; }
   Foo(int x, int y) { std::cout << "Foo(int = " << x << ", int = " << y << ")" << std::endl; }
};

int main()                 // Output
{                          // ---------------------
   auto a = Foo();         // Foo()
   auto b = Foo(1);        // Foo(int = 1)
   auto c = Foo(2, 3);     // Foo(int = 2, int = 3)
   auto d = Foo{};         // Foo()
   auto e = Foo{1};        // Foo(int = 1)
   auto f = Foo{2, 3};     // Foo(int = 2, int = 3)
   auto g = Foo({});       // Foo(int = 0)          <<< Why?
   auto h = Foo({1});      // Foo(int = 1)
   auto i = Foo({2, 3});   // Foo(int = 2, int = 3)
}

behave as commented.

From cppreference: cpp/language/list initialization:

[...]

T( { arg1, arg2, ... } )    (7)

[...]

The effects of list initialization of an object of type T are:

If T is an aggregate type, aggregate initialization is performed.

Otherwise, If the braced-init-list is empty and T is a class type with a default constructor, value-initialization is performed.

[...]

I concluded that Foo({}) should call the default constructor.

Where's the bug?

Columbo
  • 57,033
  • 7
  • 145
  • 194
precarious
  • 578
  • 2
  • 13
  • 4
    The bug is in cppreference. – Casey Nov 06 '14 at 20:14
  • 2
    Fixed cppreference to read "The effects of list initialization of an object of type `T` from a *non-parenthesized braced-init-list* are..." would that have been clearer? – Casey Nov 06 '14 at 20:30
  • @Casey I would say no. And would say that `T({...})` is *not* a list initialization of an object of type `T`, rather than to add vague wording that could be misinterpreted. Cpprefrerence already doesn't claim it to be. But with your proposed fix, it would read that `T({...})` is a list initialization of an object of type `T` from a parenthesized braced init list, which is *not* the case. It is a list initialization of whatever parameter the chosen constructor has. – Johannes Schaub - litb Nov 06 '14 at 22:07
  • @Casey alternatively it might be worth to show multiple initializer lists, `T({...}, {...}, ...)` (aswell as in the function case and whereever multiple lists make sense. just as done with `arg1, arg2 ...`). Then it can't be confused as a list initialization of an object of type `T` so easily anymore, I think. – Johannes Schaub - litb Nov 06 '14 at 22:16
  • @JohannesSchaub-litb Quite the contrary, Cppreference *did* in fact claim that `T({args...})` is list-initialization. It was listed (pun intended) on [the "list-initialization" page as alternative 7 of the situations in which list-initialization is performed](http://en.cppreference.com/mwiki/index.php?title=cpp/language/list_initialization&oldid=72490). I think the OP's reading of the page was accurate. The *proper* fix may be to remove items 5, 6, and 7 from the page altogether. – Casey Nov 06 '14 at 22:21
  • @JohannesSchaub-litb [Is this better](http://en.cppreference.com/mwiki/index.php?title=cpp/language/list_initialization&oldid=73604)? The other syntaxes are discussed on the [Overload Resolution](http://en.cppreference.com/w/cpp/language/overload_resolution) page. It would seem that whoever made the list initialization page in the first place was enumerating all cases where a braced-init-list can appear in the grammar, regardless of whether those cases actually involve list-initialization. – Casey Nov 06 '14 at 23:08
  • @Casey hm, it certainly *is* list initialization, and it still (IMO) correctly lists it as such. But not list initialization of an object of type `T`. And indeed the page does not claim that it is. Just as it doesn't claim that alternative 5 initializes a function (perhaps more obvious here). – Johannes Schaub - litb Nov 06 '14 at 23:51
  • 2
    Perhaps it might be a good idea to replace `T({...})` by `U({...})` to avoid the problem with the text referring to `T`, which here is not right. – Johannes Schaub - litb Nov 06 '14 at 23:59
  • @Casey BTW I just added a comment on the overload resolution page. – Johannes Schaub - litb Nov 07 '14 at 00:06
  • 1
    @Casey and litb, I restored and rearranged the [list-initialization](http://en.cppreference.com/w/cpp/language/list_initialization) bullet points and made the wording more specific about what's getting initialized where. – Cubbi Nov 07 '14 at 05:50
  • @Cubbi, JohannesSchaub-litb, and Casey Thank you all for clarifying the wording at cppreference.com. Now, referring to "U's constructor's parameter" in case (4) does not seem to be precise since Foo({}) will invoke the default constructor if Foo(int) is removed from the example above. – precarious Nov 07 '14 at 06:55
  • @cubbi much better. But case 4 is a function call to a constructor, and *copy* list initializes the parameter. What is direct initialized is the U object by non list initialization. – Johannes Schaub - litb Nov 07 '14 at 09:19
  • 1
    @precarious it still is precise. if the int constructor is removed, you are going to call the copy/move constructor. – Johannes Schaub - litb Nov 07 '14 at 09:38
  • @litb right, moved (4) into copy-list-init block. – Cubbi Nov 07 '14 at 11:13
  • @JohannesSchaub-litb Thank you for clarification! :-) – precarious Nov 07 '14 at 16:09
  • @JohannesSchaub-litb What about precision with respect to more than one argument? It seems as if `Foo({2, 3})` invokes `Foo(int, int)`. – precarious Nov 07 '14 at 16:37
  • @JohannesSchaub-litb I see it now, nevermind. :-) – precarious Nov 07 '14 at 17:02
  • @Cubbi That's much better - thanks for this fix and for all the effort you put into cppreference. – Casey Nov 07 '14 at 17:37
  • @Casey btw it would've helped if a link to this SO post accompanied the first edits. It wasn't clear where the confusion originated. – Cubbi Nov 07 '14 at 17:40

1 Answers1

18

The default constructor is only applicable if you use one single pair of either braces:

auto a = Foo();         // Foo()
auto b = Foo{};         // Foo()

Foo({}) instead will only call constructors with the empty list as the argument, copy-list-initializing the parameter of whatever constructor is chosen. [dcl.init]/16:

If the destination type is a (possibly cv-qualified) class type:
— If the initialization is direct-initialization […] constructors are considered. The applicable constructors are enumerated (13.3.1.3), and the best one is chosen through overload resolution (13.3). The constructor so selected is called to initialize the object, with the initializer expression or expression-list as its argument(s). If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.

You have one argument: The empty braced-init-list. There is a list-initialization sequence converting {} to int so the constructor Foo(int) is chosen by overload resolution. The parameter is initialized to zero as {} implies a value-intialization which, for scalars, implies a zero-initialization.

There is no bug in cppreferences documentation either: For (7) it is stated that

7) in a functional cast expression or other direct-initialization, with braced-init-list used as the constructor argument

Which clearly leads to the same result as with the above quote: The constructor is called with the (empty) braced-init-list.

Columbo
  • 57,033
  • 7
  • 145
  • 194
  • 6
    +1 To clarify for OP, the code is equivalent in this case to `auto g = Foo(int{});`, and `int{} == 0`. – cdhowie Nov 06 '14 at 20:22
  • 1
    speaking of non-standard approaches, in `Visual Studio / msvc`, when the "list" contains 1 literal that can be interpreted as an integral type, e.g. `{42}`, that is not a list - as it should be according to the standard - but it's a "real" integer as far as the compiler - msvc - is concerned; example `auto a = {1}`, `a` it's an integer if you use `msvc` . – user2485710 Nov 06 '14 at 20:36
  • @user2485710: Are you sure that's nonstandard? While I wasn't paying careful attention, I do believe I've read `auto a = {1}` is *supposed* to deduce `int` as the type. –  Nov 06 '14 at 21:25
  • 2
    @Hurkyl the expected outcome in that particular case is an error because `{}` doesn't really has a type therefore according to the standard the compiler is supposed to see `auto` as a request for type deduction, `a` as a label, `=` as an assignment/copy ctor, `{1}` as something without a type with a literal in it; therefore the compiler shouldn't be able to deduce any type for `a` and that's why it's supposed to generate an error instead of an instance of an integral type . Microsoft added this exception to the rule and I don't think that this exception will make the use of `{}` better . – user2485710 Nov 06 '14 at 21:33
  • 1
    @user2485710 Initializer lists don't have a type because they're not expressions. But auto a = {1} is well-formed, and should not produce an error. The type should be deduced as std::initializer_list (it's a much debated special case). If Microsoft deduces int, that's the bug. But you don't have to expect an error. – gigabytes Nov 06 '14 at 21:52
  • 1
    Post-[N3922](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3922.html), `auto a = {1};` should deduce a `std::initializer_list`, `auto a{1};` should deduce an `int`, and `auto a{1,2};` should be ill-formed. – T.C. Nov 07 '14 at 05:15
  • @T.C. and `auto a{(1,2)}` will be an `int`. :p – Yakk - Adam Nevraumont Nov 07 '14 at 14:45
  • Note: A similar pitfall occurs with the assignment operator; `a = {};` will prefer `operator=(int)` over copy-assigning a temporary `Foo()` – M.M Mar 18 '16 at 01:12
  • So, if `({})` parameter list is interpreted as a list with a single argument, then the question shifts to the last initialization in the OP example: `Foo({2, 3})`. According to the OP, it calls the `Foo::Foo(int x, int y)` constructor. Why? – AnT Mar 18 '16 at 01:24
  • Oh, got it. Conceptually it is interpreted as `Foo(Foo{2 , 3})` meaning that *copy constructor* is the single-argument constructor that wins overload resolution in this case. – AnT Mar 18 '16 at 01:31
  • @AnT The move constructor, actually. You can observe that [with `-fno-elide-constructors`](http://coliru.stacked-crooked.com/a/bc5e4a80a34de8ed). – Columbo Mar 18 '16 at 01:33