Reputation: 16691
Which constructor should be called in the following code and why?
struct S
{
int i;
S() = default;
S(void *) : i{1} { ; }
};
S s{{}};
If I use clang
(from trunk), then the second one is called.
If the second constructor is commented out, then S{{}}
is still valid expression, but (I believe) move-constructor from default-constructed instance of S{}
is called in the case.
Why conversion constructor has priority over the default one in the very first case?
The intention of such a combination of the constructors of S
is to save its std::is_trivially_default_constructible_v< S >
property, except a finite set of cases, when it should be initialized in a certain way.
Upvotes: 12
Views: 1697
Reputation: 302643
If the second constructor is commented out, then S{{}} is still valid expression, but (I sure) move-constructor from default-constructed instance of S{} is called in the case.
Actually, that's not what happens. The ordering in [dcl.init.list] is:
List-initialization of an object or reference of type T is defined as follows:
— If T is an aggregate class and the initializer list has a single element of type cv U, [...]
— Otherwise, if T is a character array and [...]
— Otherwise, if T is an aggregate, aggregate initialization is performed (8.6.1).
Once you remove the S(void *)
constructor, S
becomes an aggregate - it has no user-provided constructor. S() = default
doesn't count as user-provided because reasons. Aggregate initialization from {}
will end up value-initializing the i
member.
Why conversion constructor has priority over the default one in the very first case?
With the void*
remaining, let's keep going down the bullet list:
— Otherwise, if the initializer list has no elements [...]
— Otherwise, if T is a specialization of std::initializer_list, [...]
— Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution (13.3, 13.3.1.7).
[over.match.list] gives us a two-phase overload resolution process:
— Initially, the candidate functions are the initializer-list constructors (8.6.4) of the class T and the argument list consists of the initializer list as a single argument.
— If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.If the initializer list has no elements and T has a default constructor, the first phase is omitted.
S
doesn't have any initializer list constructors, so we go into the second bullet and enumerate all the constructors with the argument list of {}
. We have multiple viable constructors:
S(S const& );
S(S&& );
S(void *);
The conversion sequences are defined in [over.ics.list]:
Otherwise, if the parameter is a non-aggregate class X and overload resolution per 13.3.1.7 chooses a single best constructor C of X to perform the initialization of an object of type X from the argument initializer list:
— If C is not an initializer-list constructor and the initializer list has a single element of type cv U, [...] — Otherwise, the implicit conversion sequence is a user-defined conversion sequence with the second standard conversion sequence an identity conversion.
and
Otherwise, if the parameter type is not a class: [...] — if the initializer list has no elements, the implicit conversion sequence is the identity conversion.
That is, the S(S&& )
and S(S const& )
constructors are both user-defined conversion sequences plus identity conversion. But S(void *)
is just an identity conversion.
But, [over.best.ics] has this extra rule:
However, if the target is
— the first parameter of a constructor or
— the implicit object parameter of a user-defined conversion function
and the constructor or user-defined conversion function is a candidate by
— 13.3.1.3, when [...]
— 13.3.1.4, 13.3.1.5, or 13.3.1.6 (in all cases), or
— the second phase of 13.3.1.7 when the initializer list has exactly one element that is itself an initializer list, and the target is the first parameter of a constructor of classX
, and the conversion is toX
or reference to (possibly cv-qualified)X
,user-defined conversion sequences are not considered.
This excludes from consideration S(S const&)
and S(S&& )
as candidates - they are precisely this case - the target being the first parameter of the constructor as a result of the second phase of [over.match.list] and the target being a reference to possibly cv-qualified S
, and such a conversion sequence would be user-defined.
Hence, the only remaining candidate is S(void *)
, so it's trivially the best viable candidate.
Upvotes: 10