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


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.