Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 483
Tomilov Anatoliy Avatar asked Mar 03 '17 19:03

Tomilov Anatoliy


People also ask

What is double brace initialization?

Double brace initialisation creates an anonymous class derived from the specified class (the outer braces), and provides an initialiser block within that class (the inner braces). e.g. new ArrayList<Integer>() {{ add(1); add(2); }};

What does double brace mean?

Double brackets or [[]] in math refer to rounding off the value inside to its greatest integer less than or equal to the value. For example: [[7]]=7[[6.987]]=6[[3.225]]=3.

What do double brackets mean in Java?

Double brace initialization is a combination of two separate process in java. There are two { braces involved in it. If you see two consecutive curly braces { in java code, it is an usage of double brace initialization. Java Double Brace Initialization. First brace is creation of an anonymous inner class.


1 Answers

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.

like image 169
Barry Avatar answered Oct 03 '22 23:10

Barry