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 you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With