Why scalar in braces are not interpeted as initializer_list - c++

Consider the following code snippet:
#include <iostream>
#include <initializer_list>
struct C
{
C(std::initializer_list<int>) { std::cout << "list\n"; }
C(std::initializer_list<int>, std::initializer_list<int>) { std::cout << "twice-list\n"; }
};
int main()
{
C c1 { {1,2}, {3} }; // twice-list ctor
C c2 { {1}, {2} }; // why not twice-list ?
return 0;
}
Live demo.
Why scalar values in braces for c2 variable are not interpreted as separate std::initializer_list?

First, something very important: You have two different kinds of constructors. The first in particular, C(std::initializer_list<int>), is called an initializer-list constructor. The second is just a normal user-defined constructor.
[dcl.init.list]/p2
A constructor is an initializer-list constructor if its first parameter is of type std::initializer_list<E> or reference to possibly cv-qualified std::initializer_list<E> for some type E, and either there are no other parameters or else all other parameters have default arguments (8.3.6).
In a list-initialization containing one or more initializer-clauses, initializer-list constructors are considered before any other constructors. That is, initializer-list constructors are initially the only candidates during overload resolution.
[over.match.list]/p1
When objects of non-aggregate class type T are list-initialized such that 8.5.4 specifies that overload resolution is performed according to the rules in this section, overload resolution selects the constructor in two phases:
Initially, the candidate functions are the initializer-list constructors (8.5.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.
So for both declarations of c1 and c2 the candidate set consists only of the C(std::initializer_list<int>) constructor.
After the constructor is selected the arguments are evaluated to see if there exists an implicit conversion sequence to convert them to the parameter types. This takes us to the rules for initializer-list conversions:
[over.ics.list]/p4 (emphasis mine):
Otherwise, if the parameter type is std::initializer_list<X> and all the elements of the initializer list can be implicitly converted to X, the implicit conversion sequence is the worst conversion necessary to convert an
element of the list to X, or if the initializer list has no elements, the identity conversion.
This means a conversion exists if each element of the initializer list can be converted to int.
Let's focus on c1 for now: For the initializer-list {{1, 2}, {3}}, the initializer-clause {3} can be converted to int ([over.ics.list]/p9.1), but not {1, 2} (i.e int i = {1,2} is ill-formed). This means the condition for the above quote is violated. Since there is no conversion, overload resolution fails since there are no other viable constructors and we are taken back to the second phase of [over.match.list]/p1:
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.
Notice the change in wording at the end. The argument list in the second phase is no longer a single initializer-list, but the arguments of the braced-init-list used in the declaration. This means we can evaluate the implicit conversions in terms of the initializer-lists individually instead of at the same time.
In the initializer-list {1, 2}, both initializer-clauses can be converted to int, so the entire initializer-clause can be converted to initializer_list<int>, the same for {3}. Overload resolution is then resolved with the second constructor being chosen.
Now let's focus on c2, which should be easy now. The initializer-list constructor is first evaluated, and, using { {1}, {2} } there surely exists a conversion to int from {1} and {2}, so the first constructor is chosen.

C c2 { {1}, {2} };
This line does not pass in two arguments of std::initializer_list<int>, but rather, it is passing in one std::initializer_list<std::initializer_list<int> >. A solution would be to instead instantiate c2 like so:
C c2({1}, {2});

Related

Is assign with braces the same as call the constructor?

I know that for scalar types you can assign values with braces like int a { 0 };.
This helps with cast, type conversion ecc.
But what for udt? Is
shared_ptr<int> myIntSmartPtr { my_alloc(42), my_free };
the same as
shared_ptr<int> myIntSmartPtr = shared_ptr<int>(my_alloc(42), my_free);
The braces should call the constructor, right?
Is it like an initializer list?
I know what an std::initializer_list is, but it must be the same type T, while in { my_alloc(42), my_free } the types diverge.
This is direct list initialization.
shared_ptr<int> myIntSmartPtr { my_alloc(42), my_free };
This is an example of the first syntax:
T object { arg1, arg2, ... }; (1)
The exact effect it has is therefore
List initialization is performed in the following situations:
direct-list-initialization (both explicit and non-explicit constructors are considered)
initialization of a named variable with a braced-init-list (that is, a possibly empty brace-enclosed list of expressions or nested braced-init-lists)
And for more detail about what that actually means:
The effects of list-initialization of an object of type T are:
... [A bunch of cases that don't apply]
Otherwise, the constructors of T are considered, in two phases:
All constructors that take std::initializer_list as the only argument, or as the first argument if the remaining arguments have default values, are examined, and matched by overload resolution against a single argument of type std::initializer_list
If the previous stage does not produce a match, all constructors of T participate in overload resolution against the set of arguments that consists of the elements of the braced-init-list, with the restriction that only non-narrowing conversions are allowed. If this stage produces an explicit constructor as the best match for a copy-list-initialization, compilation fails (note, in simple copy-initialization, explicit constructors are not considered at all).
std::shared_ptr does not have a constructor that takes an std::initializer_list, so the second bullet point applies and it's constructed from the arguments therein.

C++11 uniform initialization: ambiguity between initializer list and multiple-parameter constructors?

Currently trying to wrap my head around C++11's uniform initialization. I came upon this ambiguous case: consider a class which can either be constructed from either a two-argument constructor or an initializer list of any length:
class Foo {
public:
Foo(int a, int b) {
std::cout << "constructor 1" << std::endl;
}
Foo(std::initializer_list<int>) {
std::cout << "constructor 2" << std::endl;
}
};
Following uniform initialization convention, I'd expect the following to work:
Foo a (1, 2) prints constructor 1 (duh)
Foo b {1, 2} prints constructor 1
Foo c = {1, 2} prints constructor 2
However, it seems like the compiler interprets Foo b {1, 2} as a list initialization, and calls constructor 2. Is the () syntax the only way to force the compiler to consider other kinds of constructors when an initializer-list constructor is present?
it seems like the compiler interprets Foo b {1, 2} as a list
initialization, and calls constructor 2. Is the () syntax the only way
to force the compiler to consider other kinds of constructors when an
initializer-list constructor is present?
Quotes from standard draft explains this well:
9.4.5.2 [dcl.init.list] (emphasis mine):
A constructor is an initializer-list constructor if its first
parameter is of type std​::​initializer_­list or reference to cv
std​::​initializer_­list for some type E, and either there are no
other parameters or else all other parameters have default arguments
([dcl.fct.default]).
[Note 2: Initializer-list constructors are
favored over other constructors in list-initialization
([over.match.list]). Passing an initializer list as the argument to
the constructor template template C(T) of a class C does not
create an initializer-list constructor, because an initializer list
argument causes the corresponding parameter to be a non-deduced
context ([temp.deduct.call]). — end note]
and 12.4.2.8 [over.match.list]:
When objects of non-aggregate class type T are list-initialized such
that [dcl.init.list] specifies that overload resolution is performed
according to the rules in this subclause or when forming a
list-initialization sequence according to [over.ics.list], overload
resolution selects the constructor in two phases:
If the initializer list is not empty or T has no default constructor,
overload resolution is first performed where the candidate functions
are the initializer-list constructors ([dcl.init.list]) of the class T
and the argument list consists of the initializer list as a single
argument.
Otherwise, or 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.
You can add an extra ignored argument to your constructor to specify a particular overload at callsite, like they do in STL:
#include <iostream>
struct non_init_list_t {};
inline constexpr non_init_list_t non_init_list;
struct Class {
Class(int a, int b, non_init_list_t = non_init_list) { std::clog << "()\n"; }
Class(std::initializer_list<int> list) { std::clog << "{}\n"; }
};
Class a{12, 42, non_init_list}; // ()
Class b{12, 42}; // {}
Class c(12, 42); // ()
If the constructor has the initializer_list version, the compiler will first interpret it as initializer_list, if there is no initializer_list version, the compiler will interpret it as another overloaded version.
If the compiler interprets it as another version, and you want to call a constructor that uses the initializer_list version, it happens that the number and type of arguments are the same as other ctors, then your will cause a bug. Then the compiler chooses the initializer_list version or other version? So using bracket notation is definitely not the initializer_list version. If you don't have an initializer_list version in your constructor, don't worry about this issue.
BTW, if you use auto to infer type automatically, DO NOT use uniform initialization. It must interpret type to initializer_list.

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.
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.

std::initializer_list type deduction

Recently I wrote a very simple class.
class C
{
public:
void AddString(std::initializer_list<std::pair<const char*,int>> x)
{
//irrelevant
}
};
int main()
{
C c;
c.AddString({ {"1",1}, {"2", 2}, {"3", 3} });
.... //other unimportant stuff
return 0;
}
To my pleasant surprise it compiled and worked correctly. Can someone please explain to me how the compiler was able to deduce the nested braced initializers were for a std::pair? I am using MSVS 2013.
c.AddString({ {"1",1}, {"2", 2}, {"3", 3} });
You're passing a braced-init-list, which itself contains nested brace-init-lists to AddString. The argument can match the std::initializer_list<std::pair<const char*,int>> parameter if the inner braced-init-lists can be converted to std::pair<const char*,int>.
This process of overload resolution occurs in two steps; first an attempt is made to match constructors of std::pair that take an std::initializer_list argument. Since std::pair has no such constructor, the second step occurs, where the other constructors of std::pair<const char*,int> are enumerated with char const[2] and int as the arguments. This will match the following pair constructor because char const[2] is implicitly convertible to char const * and the constructor itself is not explicit.
template< class U1, class U2 >
constexpr pair( U1&& x, U2&& y );
Quoting N3337 §13.3.1.7/1 [over.match.list]
When objects of non-aggregate class type T are list-initialized (8.5.4), overload resolution selects the constructor in two phases:
— Initially, the candidate functions are the initializer-list constructors (8.5.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. In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed.

What language rules permit C++11 to deduce that this is an initializer_list of pairs?

In C++11, it seems like it's legal to initialize a std::map<std::string, int> as follows:
std::map<std::string, int> myMap = {
{ "One", 1 },
{ "Two", 2 },
{ "Three", 3 }
};
Intuitively, this makes sense - the brace-enclosed initializer is a list of pairs of strings, and std::map<std::string, int>::value_type is std::pair<std::string, int> (possibly with some const qualifications.
However, I'm not sure I understand how the typing works here. If we eliminate the variable declaration here and just have the brace-enclosed initializer, the compiler wouldn't know that it was looking at a std::initializer_list<std::pair<std::string, int>> because it wouldn't know that the braced pairs represented std::pairs. Therefore, it seems as though the compiler is somehow deferring the act of assigning a type to the brace-enclosed initializer until it has enough type information from the std::map constructor to realize that the nested braces are for pairs. I don't remember anything like this happening in C++03; to the best of my knowledge, the type of an expression never depended on its context.
What language rules permit this code to compile correctly and for the compiler to determine what type to use for the initializer list? I'm hoping for answers with specific references to the C++11 spec, since it's really interesting that this works!
Thanks!
In the expression
std::map<std::string, int> myMap = {
{ "One", 1 },
{ "Two", 2 },
{ "Three", 3 }
};
on the right side you have a braced-init-list where each element is also a braced-init-list. The first thing that happens is that the initializer list constructor of std::map is considered.
map(initializer_list<value_type>,
const Compare& = Compare(),
const Allocator& = Allocator());
map<K, V>::value_type is a typedef for pair<const K, V>, in this case pair<const string, int>. The inner braced-init-lists can be successfully converted to map::value_type because std::pair has a constructor that takes references to its two constituent types, and std::string has an implicit conversion constructor that takes a char const *.
Thus the initializer list constructor of std::map is viable, and the construction can happen from the nested braced-init-lists.
The relevant standardese is present in §13.3.1.7/1 [over.match.list]
When objects of non-aggregate class type T are list-initialized (8.5.4), overload resolution selects the constructor in two phases:
— Initially, the candidate functions are the initializer-list constructors (8.5.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.
The first bullet is what causes the initializer_list constructor of map to be selected for the outer braced-init-list, while the second bullet results in the selection of the correct pair constructor for the inner braced-init-lists.
This is list-initialization. The rules are found in §8.5.4[dcl.init.list]/p3 of the standard:
List-initialization of an object or reference of type T is defined as
follows:
If the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
Otherwise, if T is an aggregate, aggregate initialization is performed (8.5.1). [example omitted]
Otherwise, if T is a specialization of std::initializer_list<E>, an initializer_list object is constructed as described below and
used to initialize the object according to the rules for
initialization of an object from a class of the same type (8.5).
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). If a narrowing
conversion (see below) is required to convert any of the arguments,
the program is ill-formed.
[example and remainder of the rules omitted]
Note that overload resolution will prefer std::initializer_list constructors in these cases (§13.3.1.7 [over.match.list]).
Thus when the compiler sees an braced list used to initialize a object of a non-aggregate, non-std::initializer_list class type, it will perform overload resolution to select the appropriate constructor, preferring the initializer_list constructor if a viable one exists (as it does for std::map).