Search code examples
c++c++11initializer-list

Silent breaking of constructor calls after adding initializer_list constructor


Let's consider the following:

#include <iostream>
#include <initializer_list>

class Foo {
public:
    Foo(int) {
        std::cout << "with int\n";
    }
};

int main() {
    Foo a{10};  // new style initialization
    Foo b(20);  // old style initialization
}

Upon running it prints:

with int
with int

All good. Now due to new requirements I have added a constructor which takes an initializer list.

Foo(std::initializer_list<int>) {
    std::cout << "with initializer list\n";
}

Now it prints:

with initializer list
with int

So my old code Foo a{10} got silently broken. a was supposed to be initialized with an int.

I understand that the language syntax is considering {10} as a list with one item. But how can I prevent such silent breaking of old code?

  1. Is there any compiler option which will give us warning on such cases? Since this will be compiler specific I'm mostly interested with gcc. I have already tried -Wall -Wextra.
  2. If there is no such option then do we always need to use old style construction, i.e. with () Foo b(20), for other constructors and use {} only when we really meant an initializer list?

Solution

  • It's impossible to generate any warning in these cases, because presented behaviour of choosing std::initializer_list constructor over direct matches is well defined and compliant with a standard.

    This issue is described in detail in Scott Meyers Effective Modern C++ book Item 7:

    If, however, one or more constructors declare a parameter of type std::initializer_list, calls using the braced initialization syntax strongly prefer the overloads taking std::initializer_lists. Strongly. If there’s any way for compilers to construe a call using a braced initializer to be to a constructor taking a std::initializer_list, compilers will employ that interpretation.

    He also presents a few of edge cases of this issue, I strongly recommend reading it.