Подтвердить что ты не робот

Тихая разбивка вызовов конструктора после добавления конструктора initializer_list

Рассмотрим следующее:

#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
}

После запуска он печатает:

with int
with int

Все хорошо. Теперь из-за новых требований я добавил конструктор, который принимает список инициализаторов.

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

Теперь он печатает:

with initializer list
with int

Итак, мой старый код Foo a{10} получил молчащий сломан. a должен был быть инициализирован с помощью int.

Я понимаю, что синтаксис языка рассматривает {10} как список с одним элементом. Но как я могу предотвратить такое молчаливое нарушение старого кода?

  • Есть ли какой-либо параметр компилятора, который даст нам предупреждение о таких случаях? Поскольку это будет специфичным для компилятора, меня больше всего интересует gcc. Я уже пробовал -Wall -Wextra.
  • Если такой опции нет, нам всегда нужно использовать конструкцию старого стиля, т.е. с () Foo b(20), для других конструкторов и использовать {} только тогда, когда мы действительно имели в виду список инициализаторов?
4b9b3361

Ответ 1

Невозможно создать предупреждение в этих случаях, потому что представленное поведение выбора конструктора std::initializer_list по прямым совпадениям хорошо определено и соответствует стандарту.

Эта проблема подробно описана в Скотт Майерс Эффективная современная книга на С++  Пункт 7:

Если, однако, один или несколько конструкторов объявляют параметр типа std::initializer_list, вызовы с использованием синтаксиса принудительной инициализации сильно предпочитают перегрузки с использованием std:: initializer_lists. Сильно. Если для компиляторов любой способ интерпретировать вызов, используя инициализатор должен быть конструктором, принимающим std::initializer_list, компиляторы будут использовать эту интерпретацию.

Он также представляет несколько краев этого вопроса, я настоятельно рекомендую прочитать его.

Ответ 2

Я не мог найти такой вариант, поэтому, видимо, в таких случаях вы должны использовать круглые скобки для классов с конструктором initializer_list и равномерной инициализацией для всех остальных классов по вашему желанию.

В этом ответе и комментариях можно найти некоторые полезные сведения: fooobar.com/questions/31483/...

Ответ 3

Нет предупреждений компилятора, и их никогда не будет. Просто не имеет смысла предупреждать о том, что код делает что-то общее, например

std::vector vec{1};

Помните, что компилятор только предупреждает о действительно нежелательных материалах, например о стиле undefined. Он не знает, что в вышеприведенном определении вы хотели вызвать конструктор с аргументом размера. Для всех он знает, что вы действительно хотите иметь вектор с одним элементом! Он не может читать ваши мысли:)

Ответ на ваш второй вопрос в основном да. Вы всегда можете добавить фиктивный параметр, например struct {} dummy;, чтобы избежать использования конструктора с списком инициализаторов, но на самом деле единственным решением является использование скобок вместо скобок (или не сломайте интерфейс внезапно).

Если вы хотите изменить каждую часть кода, которая использует инициализацию списка, вы можете удалить конструктор списка инициализаторов, изменить их на фигурные скобки и затем правильно реализовать конструктор. Я считаю, что такое изменение является нарушающим, и справиться с ним должным образом. Другая идея заключалась в том, чтобы заранее придумать пример использования списка инициализаторов и реализовать его сразу.