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

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

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

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

Ответ 1

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

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

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

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

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