Что может пойти не так, если инициализация copy-list допускает явные конструкторы?

в стандарте C++, §13.3.1.7 [за.спичка.список], указано следующее:

в copy-list-инициализации, если explicit конструктор выбран, инициализация плохо сформирована.

это причина, почему мы не можем сделать, например, что-то вроде этого:

struct foo {
    // explicit because it can be called with one argument
    explicit foo(std::string s, int x = 0);
private:
    // ...
};

void f(foo x);

f({ "answer", 42 });

(обратите внимание, что здесь происходит не преобразование, и он не был бы одним, даже если бы конструктор был "неявным". Это инициализация a foo объект, используя конструктор напрямую. Кроме std::string, здесь нет преобразования.)

это кажется мне совершенно прекрасным. Не может быть, чтобы неявное обращение укусило меня.

если { "answer", 42 } может инициализировать что-то еще, компилятор не предаст меня и не сделает неправильно:

struct bar {
    // explicit because it can be called with one argument
    explicit bar(std::string s, int x = 0);
private:
    // ...
};

void f(foo x);
void f(bar x);

f({ "answer", 42 }); // error: ambiguous call

нет проблем: вызов неоднозначен, код не будет компилироваться, и мне придется выбрать перегрузку явно.

f(bar { "answer", 42 }); // ok

поскольку запрет явно указан, у меня такое чувство, что мне чего-то не хватает. Насколько я вижу, выбор явных конструкторов инициализации списка не кажется мне проблемой: используя синтаксис инициализации списка, программист уже выражает желание сделать какое-то"преобразование".

что может пойти не так? Что я упускаю?

4 ответов


концептуально copy-list-initialization-это преобразование составного значения в тип назначения. В документе, который предложил формулировку и объяснил обоснование, уже рассматривался термин "копия" в "инициализации списка копий", поскольку он на самом деле не передает фактического обоснования этого. Но он сохраняется для совместимости с существующими формулировками. А {10, 20} значение пары / кортежа не должно быть в состоянии скопировать инициализировать String(int size, int reserve), потому что строка не является парой.

явный конструкторы считаются, но их использование запрещено. Это имеет смысл в следующих случаях

struct String {
  explicit String(int size);
  String(char const *value);
};

String s = { 0 };

0 не передает значение строки. Таким образом, это приводит к ошибке, потому что и конструкторы считаются, но explicit конструктор выбран, вместо 0 обрабатывается как константа нулевого указателя.

к сожалению, это также происходит при разрешении перегрузки по функциям

void print(String s);
void print(std::vector<int> numbers);

int main() { print({10}); }

это плохо сформированные тоже из-за двусмысленности. Некоторые люди (включая меня) до выпуска C++11 Думали, что это неудачно, но не придумали документ, предлагающий изменение в отношении этого (насколько мне известно).


такое заявление:

в инициализации copy-list -, если explicit выбран конструктор, инициализация плохо сформирована.

означает много вещей. Среди них, это означает, что он должен посмотреть в явных конструкторов. В конце концов, он не может выбрать явный конструктор, если он не может посмотреть на него. Когда он ищет кандидатов для преобразования связанного списка в, он должен выбрать из всех кандидатов. Даже те, которые будут позже будет признано незаконным.

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


как я понимаю, цель сайта явно отрицает неявное приведение с помощью этого конструктора.

Итак, вы спрашиваете, почему явный конструктор не может использоваться для неявного приведения? Очевидно, потому что автор этого конструктора явно отрицал это, используя ключевое слово явно С ним. Цитата из стандарта, который вы опубликовали, просто гласит, что явно ключевое слово применяется также к спискам инициализаторов (не только к простые значения некоторого типа).

добавить:

чтобы сказать более правильно: цель ключевого слова явно используется с некоторым конструктором, что делает абсолютно ясным, что этот конструктор используется в каком-то месте (т. е. заставляет весь код явно вызывать этот конструктор).

и заявление ИМО, как f({a,b}), когда f имя функции не имеет ничего общего с явным вызовом конструктора. Это абсолютно неясно (и зависит от контекста), какой конструктор (и какой тип) используется здесь, например, это зависит от перегрузок функции.

С другой стороны что-то вроде f(SomeType(a,b)) это совсем другое дело-абсолютно ясно, что мы используем конструктор типа SomeType это требует двух аргументов a,b и что мы используем функцию f перегрузка, которая будет лучше всего принимать один аргумент типа SomeType.

поэтому некоторые конструкторы подходят для неявного использования как f({a,b}) и другие требуют, чтобы факт их использования был абсолютно ясен читателю, поэтому мы объявляем их явно.

ADD2:

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

Э. Г.

double x = 2; // looks absolutely natural
std::complex<double> x1 = 3;  // also looks absolutely natural
std::complex<double> x2 = { 5, 1 };  // also looks absolutely natural

но

std::vector< std::set<std::string> >  seq1 = 7; // looks like nonsense
std::string str = some_allocator; // also looks stupid

не потому ли, что "явный" должен остановить неявное приведение, и вы просите его сделать неявное приведение?

вы бы задали вопрос, если бы вы указали структуру с помощью одного конструктора аргументов?