Tomilov Anatoliy
Tomilov Anatoliy

Reputation: 16691

Double brace initialization

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

Answers (1)

Barry
Barry

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 class X, and the conversion is to X 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

Related Questions