7

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?

  • 3
    Related: https://stackoverflow.com/questions/9976927/when-to-use-the-brace-enclosed-initializer -- std::vector is a "classical example", although I can find little mention of it on SO. – user202729 Oct 01 '20 at 02:07

3 Answers3

1

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);                 // ()
bipll
  • 11,747
  • 1
  • 18
  • 32
1

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.

Andreas DM
  • 10,685
  • 6
  • 35
  • 62
0

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.

Steve Wilson
  • 104
  • 4