Подтвердить что ты не робот

Разрешение перегрузки с пустым инициализатором скобок: указатель или ссылка?

Я столкнулся с реальным моментом WTF, когда обнаружил, что приведенный ниже код выводит "указатель".

#include <iostream>
#include <utility>

template<typename T>
struct bla
{
    static void f(const T*) { std::cout << "pointer\n"; }
    static void f(const T&) { std::cout << "reference\n"; }
};

int main()
{
    bla<std::pair<int,int>>::f({});
}

Изменение аргумента шаблона std::pair<int,int> для int или любого другого примитивного типа дает (по крайней мере для меня) ожидаемую ошибку "неоднозначной перегрузки". Кажется, что встроенные типы являются особенными здесь, потому что любой пользовательский тип (совокупный, нетривиальный, с дефолтным конструктором и т.д.) Все приводит к вызываемой перегрузке указателя. Я считаю, что шаблон не нужен для его воспроизведения, он просто упрощает тестирование разных типов.

Лично я не думаю, что это логично, и я ожидал бы неоднозначную ошибку перегрузки во всех случаях, независимо от аргумента шаблона. GCC и Clang (и я считаю, MSVC) все не согласны со мной, через С++ 11/14/1z. Заметьте, что я полностью осведомлен о плохом API этих двух перегрузок, и я никогда не напишу что-то вроде этого, я обещаю.

Итак, возникает вопрос: что происходит?

4b9b3361

Ответ 1

О, это противно.

Per [over.ics.list] p4 и p7:

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

[...]

6 В противном случае, если параметр является ссылкой, см. 13.3.3.1.4. [Примечание. Правила в этом разделе будут применяться для инициализации базового временного кода для ссылки. - конец примечания] [...]

[...]

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

[...]

(7.2) - если в списке инициализаторов нет элементов, неявная последовательность преобразования - это преобразование идентичности. [...]

Конструкция a const std::pair<int,int> временная из {} считается определяемой пользователем конверсией. Конструкция const std::pair<int,int> * prvalue или const int * prvalue или временного объекта const int считаются стандартными преобразованиями.

Стандартные преобразования предпочтительнее, чем пользовательские преобразования.

Ваша собственная находка Вопрос CWG 1536 имеет значение, но в основном для юристов языка. Это пробел в формулировке, где стандарт на самом деле не говорит, что происходит для инициализации ссылочного параметра из {}, так как {} не является выражением. Это не то, что делает один вызов двусмысленным, а другой - тем не менее, и реализации могут использовать здесь здравый смысл.