C++
I’ve read that constructors without the explicit
keyword and with one parameter (or a one-argument call to a ctor with several parameters, where all but one have default values) can perform one implicit conversion. Given two classes where Foo
’s ctor has two int
parameters and Bar
’s ctor has two Foo
parameters, that statement seems to imply a call to Bar
’s ctor with two unqualified sets of int
s should not translate into a call to Bar
’s ctor with two Foo
’s. Then, why does the following compile?
struct Foo {
Foo(int x_, int y_) : x(x_), y(y_) {}
int x;
int y;
};
struct Bar {
Bar(Foo first_, Foo second_) : first(first_), second(second_) {}
Foo first;
Foo second;
};
#include <iostream>
int main() {
Bar b = { { 1, 2 }, { 3, 4 } };
std::cout << b.first.x << ", " << b.first.y << ", ";
std::cout << b.second.x << ", " << b.second.y << std::endl;
// ^ Output: 1, 2, 3, 4
}
The statement implies nothing of the sort. It is a statement about constructors with one parameter, and you have written constructors with two parameters. It doesn't apply.
Any conversion sequence can contain at most one user-defined conversion (which, depending on the context of the conversion, may or may not be allowed to be
explicit
-- in the case you describe it may not). Here there are two separate conversion sequences: one to get from the initializer list to the first argument ofBar
's constructor, and another to get from the other initializer list to the second argument. Each sequence contains one conversion, toFoo
. That's why your code is fine.What you can't do under the "one user-defined conversion" rule is this:
because that would require two user-defined conversions in the chain. But
Baz baz(Foo(1));
is fine since only one conversion is needed to get from the argument you provided (an instance ofFoo
) to an argumentBaz
can accept (an instance ofBar
).Baz baz(Bar(1L))
is also fine, because although there are two conversionslong -> int -> Foo
in order to get from1L
to an argument thatBar()
can accept, one of the conversions is builtin, not user-defined.You also can't do this:
because that's copy-initialization and it first tries to convert the right-hand-side to
Bar
. Two user-defined conversions are needed in the chainint -> Foo -> Bar
, so it's no good. But you can do this:It's equivalent in this case to
Bar bar(1);
, notBar bar = 1;
. There is a difference in general betweenT t = { 1 };
andT t(1)
, which is that withT t = { 1 };
theT
constructor cannot be explicit. But it doesn't count as part of the conversion chain for the initializer.