Почему {} в качестве аргумента функции не приводит к двусмысленности?

20

Рассмотрим этот код:

#include <vector>
#include <iostream>

enum class A
{
  X, Y
};

struct Test
{
  Test(const std::vector<double>&, const std::vector<int>& = {}, A = A::X)
  { std::cout << "vector overload" << std::endl; }

  Test(const std::vector<double>&, int, A = A::X)
  { std::cout << "int overload" << std::endl; }
};

int main()
{
  std::vector<double> v;
  Test t1(v);
  Test t2(v, {}, A::X);
}

https://godbolt.org/z/Gc_w8i

Это печатает:

vector overload
int overload

Почему это не приводит к ошибке компиляции из-за неоднозначного разрешения перегрузки? Если второй конструктор удален, мы получим vector overloadдва раза. Как / по какой метрике intоднозначно лучше, {}чем для std::vector<int>?

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

Макс Лангоф
источник
Если я вспоминаю {}как блок кода, присваиваю 0 переменным - пример: const char x = {}; установлен в 0 (нулевой символ), то же самое для int и т. д.
Seti
2
@Seti Это то, что {}эффективно делает в некоторых особых случаях, но это не всегда правильно (для начала, std::vector<int> x = {};работает, std::vector <int> x = 0;нет). Это не так просто, как « {}назначить ноль».
Макс Лангхоф
Да, это не так просто, но все равно присваивает ноль - думал, что я думаю, что этот beaiour довольно запутанный и не должен использоваться на самом деле
Сети
2
@Seti struct A { int x = 5; }; A a = {};не присваивает ноль в любом смысле, он создает Aс a.x = 5. Это не похоже на то A a = { 0 };, что инициализируется a.xдо 0. Ноль не присущ {}, он присущ тому, как каждый тип создается по умолчанию или инициализируется значением. Смотрите здесь , здесь и здесь .
Макс Лангоф
Я все еще думаю, что значение, созданное по умолчанию, сбивает с толку (требует, чтобы вы проверяли поведение или постоянно держали много знаний)
Сети

Ответы:

12

Это в [over.ics.list] , акцент мой

6 В противном случае, если параметр является неагрегированным классом X и разрешение перегрузки для [over.match.list] выбирает единственный лучший конструктор C из X, чтобы выполнить инициализацию объекта типа X из списка инициализатора аргумента:

  • Если C не является конструктором списка инициализаторов, и список инициализаторов имеет единственный элемент типа cv U, где U - X или класс, производный от X, последовательность неявного преобразования имеет ранг Точного совпадения, если U - X, или ранг преобразования, если U является производным от X.

  • В противном случае последовательность неявного преобразования представляет собой определяемую пользователем последовательность преобразования, в которой вторая стандартная последовательность преобразования представляет собой преобразование идентичности.

9 В противном случае, если тип параметра не является классом:

  • [...]

  • если список инициализаторов не имеет элементов, последовательность неявного преобразования является преобразованием идентичности. [ Пример:

    void f(int);
    f( { } ); // OK: identity conversion
    

    конец примера]

std::vectorИнициализируется конструктором и пулей в смелыми считает определенный пользователем converison. Между тем, для int, это преобразование идентичности, поэтому оно превосходит звание первого c'or.

Рассказчик - Unslander Monica
источник
Да, кажется точным.
Коломбо
Интересно видеть, что эта ситуация явно рассматривается в стандарте. Я бы действительно ожидал, что это будет неоднозначно (и, похоже, это можно было легко определить таким образом). Я не могу следовать рассуждениям в вашем последнем предложении - 0имеет тип, intно не тип std::vector<int>, как это «как будто» по отношению к «нетипизированной» природе {}?
Макс Лангхоф
@MaxLanghof - Другой способ взглянуть на это то, что для типов, не относящихся к классам, это не определенное пользователем преобразование при любом растяжении. Вместо этого это прямой инициализатор для значения по умолчанию. Отсюда тождество в этом случае.
StoryTeller - Unslander Моника
Эта часть ясна. Я удивлен необходимостью пользовательского преобразования в std::vector<int>. Как вы сказали, я ожидал, что «тип параметра в конечном итоге решит, какой тип аргумента», и {}«тип» (так сказать) std::vector<int>не должен нуждаться в (не тождественном) преобразовании для инициализации a std::vector<int>. Стандарт, очевидно, говорит, что это так, но это не имеет смысла для меня. (Имейте в виду, я не утверждаю, что вы или стандарт не правы, просто пытаюсь согласовать это с моими ментальными моделями.)
Макс Лангхоф
Хорошо, это редактирование было не тем разрешением, на которое я надеялся, но достаточно справедливым. : D Спасибо за ваше время!
Макс Лангхоф