简体   繁体   中英

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, ie with () Foo b(20) , for other constructors and use {} only when we really meant an initializer list?

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.

I couldn't find such an option, so apparently in such cases you should use parentheses for classes that have initializer_list constructor, and uniform initialization for all other classes as you wish.

Some useful insights can be found in this answer and comments to it: https://stackoverflow.com/a/18224556/2968646

There are no compiler warnings and there never will be. It just doesn't make sense to warn on code doing something common like

std::vector vec{1};

Remember that the compiler only warns about really unwanted stuff, like undefined behavior. It has no way of knowing that in the definition above, you meant to call the constructor taking a size argument. For all it knows you actually want to have a vector with one element! It can't read your mind :)

The answer to your second question is basically yes. You can always add a dummy parameter like struct {} dummy; to avoid using the constructor with initializer list, but really, the only same solution is just to use parentheses instead of braces (or don't break the interface suddenly).

If you want to change every portion of code that uses list initialization, you can delete the initializer list constructor, change them to braces, and then implement the constructor correctly. I would consider such change a breaking one, and deal with it appropriately. The other idea would have been to come up with the initializer list use case beforehand, and implement it right away.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM