Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

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

0 views
Skip to first unread message

Scott Meyers

unread,
Oct 31, 2009, 2:58:15 AM10/31/09
to
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...@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]

Sean Hunt

unread,
Nov 1, 2009, 12:05:59 AM11/1/09
to
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

Johannes Schaub (litb)

unread,
Nov 1, 2009, 12:06:31 AM11/1/09
to
Scott Meyers wrote:

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.

Scott Meyers

unread,
Nov 1, 2009, 10:26:58 PM11/1/09
to
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

0 new messages