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

Что может пойти не так, если инициализация списка экземпляров разрешена явным конструкторам?

В стандарте С++, §13.3.1.7 [over.match.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 });

(Обратите внимание, что здесь происходит не преобразование, и это было бы не так, даже если конструктор был "неявным". Это инициализация объекта 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

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

Что может пойти не так? Что мне не хватает?

4b9b3361

Ответ 1

Концептуально инициализация списка копий - это преобразование составного значения в тип назначения. В документе, который предложил формулировку и объясненное обоснование, уже считалось неудачным термин "копия" в "инициализации списка копий", поскольку на самом деле он не передает фактическое обоснование. Но он поддерживается для совместимости с существующими формулировками. Значение {10, 20} pair/tuple не должно копировать инициализацию 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}); }

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

Ответ 2

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

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

ADD:

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

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

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

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

ADD2:

Моя точка зрения: иногда совершенно разумно объявлять конструкторы явными, даже если ничего не получится. ИМО, является ли конструктор явным, больше зависит от его логики, чем от каких-либо предостережений.

например.

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

Ответ 3

Это утверждение:

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

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

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

Ответ 4

Разве это не потому, что "явный" существует, чтобы остановить неявное кастинг, и вы просите его сделать неявный листинг?

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