Direct initialization with an empty initializer list

struct X { X() { std::cout << "default ctor" << std::endl; } }; int main() { X({}); } 

Will print

 default ctor 

and that makes sense because an empty binding value - initializes the object (I think). Nonetheless,

 struct X { X() { std::cout << "default ctor" << std::endl; } X(std::initializer_list<int>) { std::cout << "list initialization" << std::endl; } }; int main() { X({}); } 

For this I received

 initializer list 

I do not think this behavior is so strange, but I'm not quite sure. What is the rule for this?

Is this behavior written in some part of the standard?

+5
source share
2 answers

Is this behavior written in some part of the standard?

Sure. All this is dictated by the rules in [dcl.init] / 16 , emphasizing my correspondence to your initializer:

The semantics of initializers is as follows. The destination type is the type of the initialized object or reference, and the source type is the type of the initializer expression. If the initializer is not one (possibly in parentheses) expression, the type of the source is not defined.

  • If the initializer is ( not the brackets in brackets ) braced-init-list, the object or link is initialized with a list ([dcl.init.list]).

  • [...]

  • If the assignment type is a class of a class (possibly cv-qualit):

    • If initialization is direct initialization , or if it is copy-initialization, where the cv-unqualified version of the type source is the same class as the derived class of the destination class, the constructors are considered . The corresponding constructors are listed ([over.match.ctor]), and the best one is selected through overload resolution ([over.match]). A constructor selected in this way called to initialize an object, with an initializer expression or list expression as its argument (s). If the constructor is not used, or if overload resolution is ambiguous, initialization is poorly formed.
    • [...]

You provide an empty list of brackets in brackets, so only a later mark is applied. The constructors are considered, and in the first case, we complete the initialization of copying from the initialized default X In the latter case, initializer_list c'tor is selected as the best match. The rule for selecting this overload is specified in [over.ics.list] :

When an argument is a list of initializers ([dcl.init.list]), it is not an expression and special rules are applied to convert it to a type parameter.

If the parameter type is std :: initializer_list or "array X" and all elements of the initializer list can be implicitly converted to X, an implicit conversion sequence is the worst conversion; you must convert the list element to X. This conversion can be a user-defined transformation even in the context of calling the constructor list of initializers.

Otherwise, if the parameter is a non-aggregate class X and overloading the permission to [over.match.list] selects one best constructor of X to initialize an object of type X from the list of argument initializers, the implicit conversion sequence is a user-defined conversion sequence. If several constructors are viable but no one is better than others, the implicit sequence of transformations is an ambiguous sequence of transformations. Custom conversions allowed the conversion of initializer list elements to constructor types, except as specified in [over.best.ics].

+7
source

To find out what is really going on, declare the copies and move the constructors, compile them in C ++ 14 or earlier mode and disable copying.

Link Coliru

Output:

 default ctor move ctor 

In the first snippet, the compiler looks for X constructors that take one argument, since you provided one argument. This is a copy and move constructor, X::X(const X&) and X::X(X&&) , which the compiler will declare implicitly for you if you do not declare them yourself. The compiler then converts {} to an X object using the default constructor and passes that X object to the move constructor. (You must use fno-elide-constructors to see this, otherwise the compiler will return to the move, and in C ++ 17, copying has become mandatory.)

In the second fragment, the compiler now has a choice for converting {} to X (then calling the move constructor) or converting {} to std::initializer_list<int> (then calling the constructor of the initializer list). According to [over.ics.list] /6.2, the conversion from {} to X , which calls the default constructor, is a user-defined conversion, whereas according to [over.ics.list] / 4, the conversion from {} to std::initializer_list<int> is an identity transformation. An identity conversion is better than a custom conversion, so the compiler calls the constructor of the initializer list.

+10
source

Source: https://habr.com/ru/post/1275167/


All Articles