Topic: How many parameters in T({x, y, z}) ?


Author: Scott Meyers <usenet@aristeia.com>
Date: Sat, 31 Oct 2009 00:58:15 CST
Raw View
Consider the following attempt to construct a Widget object:

   Widget w( {5, 10, 15} );

How many arguments are being passed to the constructor?  I say one, and gcc 4.4
seems to agree with me:

#include <iostream>
#include <initializer_list>

class Widget {
public:
   Widget(int, int, int) { std::cout << "3 ints\n"; }
private:
   Widget(const Widget&) { std::cout << "copy\n"; }
};

int main()
{
   Widget w ({ 10, 20, 30 });   // error! copy ctor is private
}

Presumably the compiler is trying to create a temporary from the 3-int
constructor to pass to the copy constructor, which it thinks I'm trying to call
by passing only one constructor argument.

If this is the case, adding a constructor taking an initializer_list should make
no difference:  I'd still be calling a constructor with a single argument.  The
initializer_list constructor would be called to create the temporary, but I'd
still have to have access to the copy constructor.  But that's not what gcc does:

#include <iostream>
#include <initializer_list>

class Widget {
public:
   Widget(int, int, int) { std::cout << "3 ints\n"; }
   Widget(std::initializer_list<int>) { std::cout << "init list\n"; }
private:
   Widget(const Widget&) { std::cout << "copy\n"; }
};

int main()
{
   Widget w ({ 10, 20, 30 });   // compiles, runs, prints "init list"
}

Is this a bug in my understanding or a bug in gcc's implementation?

Thanks,

Scott





--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Sean Hunt <rideau3@gmail.com>
Date: Sat, 31 Oct 2009 22:05:59 CST
Raw View
On Oct 31, 12:58 am, Scott Meyers <use...@aristeia.com> wrote:
> Is this a bug in my understanding or a bug in gcc's implementation?
>
> Thanks,
>
> Scott

When you call "Widget w({ 10, 20, 30 })", the compiler sees two
candidates for the constructor - one taking const Widget&, and one
taking three ints. Neither of those is a direct match, but it is
possible to list-initialize a temporary of type Widget using the
triple-int constructor, and then call the copy constructor. This
behavior seems odd for constructors, but it makes sense in the
following scenario:

void foo (int, int, int);
void foo (const Widget&);

int main() {
   foo({10, 20, 30});
}

Would you really expect that call to foo to call the first declaration
of foo?

If you wish to initialize w directly from an initializer-list of three ints,
you should declare it as "Widget w {10, 20, 30};", which, as I understand it,
is the initialization syntax that will Do the Right Thing  in nearly every
scenario.

Sean Hunt


--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Johannes Schaub (litb)" <schaub-johannes@web.de>
Date: Sat, 31 Oct 2009 22:06:31 CST
Raw View
Scott Meyers wrote:

> Consider the following attempt to construct a Widget object:
>
>    Widget w( {5, 10, 15} );
>
> How many arguments are being passed to the constructor?  I say one, and
> gcc 4.4 seems to agree with me:
>
> #include <iostream>
> #include <initializer_list>
>
> class Widget {
> public:
>    Widget(int, int, int) { std::cout << "3 ints\n"; }
> private:
>    Widget(const Widget&) { std::cout << "copy\n"; }
> };
>
> int main()
> {
>    Widget w ({ 10, 20, 30 });   // error! copy ctor is private
> }
>
> Presumably the compiler is trying to create a temporary from the 3-int
> constructor to pass to the copy constructor, which it thinks I'm trying to
> call by passing only one constructor argument.
>
> If this is the case, adding a constructor taking an initializer_list
> should make
> no difference:  I'd still be calling a constructor with a single argument.
>  The initializer_list constructor would be called to create the temporary,
> but I'd
> still have to have access to the copy constructor.  But that's not what
> gcc does:
>
> #include <iostream>
> #include <initializer_list>
>
> class Widget {
> public:
>    Widget(int, int, int) { std::cout << "3 ints\n"; }
>    Widget(std::initializer_list<int>) { std::cout << "init list\n"; }
> private:
>    Widget(const Widget&) { std::cout << "copy\n"; }
> };
>
> int main()
> {
>    Widget w ({ 10, 20, 30 });   // compiles, runs, prints "init list"
> }
>
> Is this a bug in my understanding or a bug in gcc's implementation?
>
There is a subtle difference that i think is worth noting so it's clear:

Widget w{10, 20, 30};

Is different from the following

Widget w({10, 20, 30});

The first is list-initialization, and will end up in 13.3.1.7 for finding
candidates, and the second is direct initialization (because the initializer
is a braced expression list), and ends up at 13.3.1.3. So in fact the
argument in your case consists of 1 instead of 3 arguments - so only the
copy constructor is viable. For this, 13.3.3.1.5 does the mapping to the
three forms of implicit conversion sequences, and selects 13.3.3.1.5/5, and
the reference binding will make the conversion sequence a user defined
conversion sequence by going back to 13.3.3.1.5/3.

Now, if you add the initializer list, there are two viable candidate
functions, instead of only one: The initializer list constructor, and the
copy constructor. 13.3.3.1.5 maps the conversion sequence for the
initializer list constructor to an identity conversion by 13.3.3.1.5/2. In
this case, the copy constructor is still viable, but reference binding is
going back to 13.3.3.1.5/3 again (because it converts the initializer list
to "Widget" - *not* to initializer_list<int> which is merely a parameter of
the constructor used in the user defind conversion sequence.

Since using the initializer list ctor results in a better conversion
sequence, it's used over the copy constructor by GCC. I think GCC does it
all right here.

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Scott Meyers <usenet@aristeia.com>
Date: Sun, 1 Nov 2009 21:26:58 CST
Raw View
Johannes Schaub (litb) wrote:
> is a braced expression list), and ends up at 13.3.1.3. So in fact the
> argument in your case consists of 1 instead of 3 arguments
[...]
> Now, if you add the initializer list, there are two viable candidate
> functions, instead of only one: The initializer list constructor, and the
> copy constructor. 13.3.3.1.5 maps the conversion sequence for the
> initializer list constructor to an identity conversion by 13.3.3.1.5/2. In
> this case, the copy constructor is still viable, but reference binding is
> going back to 13.3.3.1.5/3 again (because it converts the initializer list
> to "Widget" - *not* to initializer_list<int> which is merely a parameter of
> the constructor used in the user defind conversion sequence.
>
> Since using the initializer list ctor results in a better conversion
> sequence, it's used over the copy constructor by GCC. I think GCC does it
> all right here.

Given your (very impressive) analysis, I agree.  It will take some time before I
internalize that

   Widget w { a, b, c };

is a constructor call with 1 or 3 arguments (depending on the constructors
offered by Widget), while

   Widget ({a, b, c});

is always a constructor call with 1 argument.

Scott

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]