Как количество фигурных скобок влияет на равномерную инициализацию?

Рассмотрим следующий фрагмент кода:

#include <iostream>

struct A {
  A() {}
  A(const A&) {}
};

struct B {
  B(const A&) {}
};

void f(const A&) { std::cout << "A" << std::endl; }
void f(const B&) { std::cout << "B" << std::endl; }

int main() {
  A a;
  f(   {a}   ); // A
  f(  {{a}}  ); // ambiguous
  f( {{{a}}} ); // B
  f({{{{a}}}}); // no matching function
}

Почему каждый вызов создает соответствующий вывод? Как количество фигурных скобок влияет на равномерную инициализацию? И как на все это влияет брекет-элизия?


person user1494080    schedule 03.02.2019    source источник
comment
Полностью приглянулся. Но я думаю, вам лучше опубликовать свое сообщение для компилятора. :)   -  person John Ding    schedule 03.02.2019
comment
@Constructor комментарии дают сообщения об ошибках при компиляции с (как минимум) g ++, иначе что написано во время выполнения   -  person bruno    schedule 03.02.2019
comment
@bruno Да, я выполнил его с лязгом, и результаты такие же.   -  person John Ding    schedule 03.02.2019
comment
может захотеть добавить тег language-lawyer   -  person Eljay    schedule 03.02.2019
comment
Nit: точки с запятой после определений функций-членов бессмысленны и позволяют непосвященным представить себе такие вещи, как все закрывающие фигурные скобки, должны их получить.   -  person Davis Herring    schedule 03.02.2019
comment
@DavisHerring Разрешить бессмысленные точки с запятой в синтаксисе было ошибкой дизайна ИМХО   -  person curiousguy    schedule 03.02.2019
comment
@curiousguy Нам повезло, что они у нас есть, иначе мы бы не смогли написать [[fallthrough]];. :)   -  person Rakete1111    schedule 03.02.2019
comment
@DavisHerring Конечно, спасибо! Я не знаю, что на меня нашло. :)   -  person user1494080    schedule 03.02.2019
comment
@ Rakete1111: Нулевые операторы используются чаще, чем нулевые объявления (которые также не являются операторами).   -  person Davis Herring    schedule 03.02.2019


Ответы (1)


Разрешение перегрузки - это весело.

  1. {a} имеет ранг точное соответствие для инициализации (временного для) параметра const A&, который превосходит определяемое пользователем преобразование B(const A&) как реализацию {a}. Это правило было добавлено в C ++ 14 для устранения неоднозначности при инициализации списка (наряду с корректировками для агрегатов).

    Обратите внимание, что условный временный объект никогда не создается: после выбора разрешения перегрузки f(const A&) ссылка будет просто инициализирован для ссылки на a, и эта интерпретация может применяться даже для некопируемых типов.

  2. Допустимо инициализировать параметр const A& (как указано выше) в конструкторе для либо A, либо B, поэтому вызов будет неоднозначным.
  3. Повторный вызов конструктора копирования (здесь A(const A&)) запрещен как несколько определяемых пользователем преобразований - вместо разрешения одного такого преобразования на каждый уровень разрешения перегрузки. Таким образом, крайние фигурные скобки должны инициализировать B из A, инициализированного из {{a}} как (разрешено) во втором случае. (Средний уровень фигурных скобок может инициализировать B, но копирование его с внешним слоем будет запрещено, и больше нечего пытаться инициализировать.)
  4. Любая интерпретация предполагает такое запрещенное дополнительное преобразование.

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

person Davis Herring    schedule 03.02.2019
comment
Просто чтобы убедиться, что я правильно понимаю: f({a}) может быть реализовано с помощью f(A{a}) или f(B{a}). Поскольку первое - идеальное совпадение, оно лучше, чем второе, и призыв однозначен. Теперь рассмотрим второй случай. f({{a}}) может быть реализован с помощью f(A{A{a}}), f(B{A{a}}) и f(B{B{a}}). Первое запрещено, а второе и третье одинаково хорошо подходят, поэтому призыв неоднозначен. Теперь рассмотрим третий случай. f({{{a}}}) может быть реализован с помощью f(A{A{A{a}}}), f(B{A{A{a}}}), f(B{B{A{a}}}) и f(B{B{B{a}}}). Только третье не запрещено. - person user1494080; 03.02.2019
comment
Думаю, я еще этого не понял. Если бы проблема заключалась в том, что f(B{A{a}}) и f(B{B{a}}) одинаково хорошо совпадают, вызов все равно был бы неоднозначным, когда мы удалим f(const A&). Однако компилятор говорит об обратном. - person user1494080; 03.02.2019
comment
@ user1494080: двусмысленность находится между f(A{A{a}}) (только внешний из этих двух определяется пользователем) и f(B{A{a}}). f(B{B{a}}) недействителен, потому что внутреннее преобразование определяется пользователем, поэтому внешнее преобразование не должно. Обратите внимание на асимметрию: {a} -> A - точное совпадение, но результирующий {B{a}} -> B определяется пользователем, потому что для идентификации {a} -> B преобразования должно было произойти разрешение перегрузки. - person Davis Herring; 04.02.2019
comment
Разве f(A{A{a}}) не вызывает два раза один и тот же конструктор копирования A(const A&)? Как один может быть определен пользователем, а другой нет? - person user1494080; 04.02.2019
comment
@ user1494080: внутренний {a} особым образом благословлен как эквивалент прямого использования a. - person Davis Herring; 04.02.2019
comment
Что вы имеете в виду, говоря, что крайние фигурные скобки должны точно соответствовать копии B, построенной из среднего набора фигурных скобок? Конструктор копирования B не упоминается. - person xskxzr; 04.02.2019
comment
Допустимо инициализировать параметр const A & в конструкторе для A или B с этим точным совпадением, ..., здесь двусмысленность не потому, что оба являются точными совпадениями, а потому, что оба преобразования являются пользовательскими. - person xskxzr; 04.02.2019
comment
@xskxzr: Ну, да: я говорил, что оба варианта жизнеспособны, а не то, что вся ICS полностью совпадает. - person Davis Herring; 04.02.2019
comment
@xskxzr: конструктор копирования выбран (и благословлен) на основе указанного типа, но фактическая инициализация параметра f знает, что это ссылка и просто связывает ссылку. - person Davis Herring; 04.02.2019
comment
Я имею в виду, что при инициализации объекта результата prvalue указанного типа конструктор копирования B не выбирается. В моем примере вы можете увидеть, что я удаляю конструктор копирования, но он все еще работает. - person xskxzr; 04.02.2019
comment
@DavisHerring Извините, я все еще борюсь. f(B{B{a}}) запрещено, потому что внутреннее преобразование определяется пользователем (я согласен), а внешнее преобразование также определяется пользователем (как я вас понимаю). Однако в третьем случае f (B {B {A {a}}}) не запрещено, потому что внутреннее преобразование является точным совпадением, среднее преобразование определяется пользователем, а внешнее преобразование снова является стандартным преобразованием? Почему первый f(B{B{...}}) определяется пользователем, а второй f(B{B{...}}) нет? - person user1494080; 04.02.2019
comment
@ user1494080: Независимо от того, является ли в f({…}) {a} или {{a}}, это не разрешено использовать определяемое пользователем преобразование в const B& при попытке интерпретировать общий аргумент как B. Но в любом случае он может преобразовать в const A&, в том числе через определяемое пользователем преобразование в трехуровневом случае. … Итак, мой анализ этого третьего случая был не совсем верным; Я его отредактирую! - person Davis Herring; 04.02.2019
comment
@xskxzr: Я не уверен, что удаленный конструктор копии будет мешать в любом случае, потому что инициализируемый объект является ссылкой, но я все равно ошибся: в любом случае только внешняя инициализация имеет B. См. Редактировать. - person Davis Herring; 04.02.2019
comment
@DavisHerring Но даже с отредактированным анализом нам нужно более одного определенного пользователем диалога в последовательности. {{a}} содержит один определяемый пользователем диалог (внешний), как объяснено для случая 2. Для инициализации B из {{a}} требуется другой определяемый пользователем диалог. Всего {{{a}}} будет содержать два пользовательских диалога. Однако, если я правильно понял, во всей последовательности разговоров разрешен только один определенный пользователем диалог. - person user1494080; 04.02.2019
comment
@ user1494080: вы получаете один «на уровень»: вы можете иметь определенную пользователем последовательность преобразования для формирования аргумента для выбран конструктор для интерпретации включающего списка инициализаторов. - person Davis Herring; 04.02.2019