Чому {} як аргумент функції не призводить до неоднозначності?


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 etc.
Seti

2
@Seti Це те, що {}ефективно робить у певних особливих випадках, але це, як правило, не правильно (для початківців, std::vector<int> x = {};працює, std::vector <int> x = 0;ні). Це не так просто, як " {}призначає нуль".
Макс Лангхоф

Правильно, це не так просто, але він все ж призначає нуль - я думав, я думаю, що це поводження досить заплутане і його не слід використовувати по-справжньому
Seti

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ініціалізується конструктором, а куля жирним шрифтом вважає конверсію, визначену користувачем. Тим часом, для an intце перетворення ідентичності, тож воно перемагає ранг першого c'tor.


Так, здається точним.
Коламбо

Цікаво побачити, що ця ситуація прямо розглядається в стандарті. Я б дійсно очікував, що це буде неоднозначним (і, схоже, це можна було б легко вказати таким чином). Я не можу дотримуватися міркувань у вашому останньому реченні - 0має тип, intале не тип std::vector<int>, як це, як "немовби", wrt до "нетипізованого" характеру {}?
Макс Лангхоф

@MaxLanghof - Інший спосіб поглянути на це - це те, що для некласових типів це не визначене користувачем перетворення на будь-якому розтягуванні. Натомість це прямий ініціалізатор для значення за замовчуванням. Звідси ідентичність у цьому випадку.
StoryTeller - Невідповідна Моніка

Ця частина зрозуміла. Я здивований необхідністю конверсії, визначеної користувачем std::vector<int>. Як ви вже говорили, я очікував, що "тип параметра в кінцевому підсумку вирішує, який тип аргументу", а {}"типу" (так би мовити) std::vector<int>не повинен потребувати (неідентифікація) перетворення для ініціалізації std::vector<int>. Стандарт, очевидно, говорить, що це робить, так що це, але це не має для мене сенсу. (Майте на увазі, я не стверджую, що ви чи стандарт помиляєтесь, просто намагаюся примирити це з моїми ментальними моделями.)
Макс Ленгоф

Гаразд, це редагування було не резолюцією, на яку я сподівався, але досить справедливою. : D Дякую за ваш час!
Макс Лангхоф
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.