Рассмотрим этот фрагмент кода С++ 11:
#include <iostream>
#include <cstddef>
template<typename T> void f(T, const char*) //#1
{
std::cout << "f(T, const char*)\n";
}
template<std::size_t N> void f(int, const char(&)[N]) //#2
{
std::cout << "f(int, const char (&)[N])\n";
}
int main()
{
f(7, "ab");
}
Хорошо, так... какая перегрузка выбрана? Прежде чем выпустить beans с выходом компилятора, попробуйте объяснить это.
(Все ссылки на разделы предназначены для окончательного стандартного документа для С++ 11, ISO/IEC 14882: 2011.)
T
из # 1 выводится на int
, N
из # 2 выводится на 3
, обе специализации являются кандидатами, обе являются жизнеспособными, настолько хорошими. Какой из них лучше?
Во-первых, рассматриваются неявные преобразования, необходимые для сопоставления аргументов функции с параметрами функции. Для первого аргумента преобразование не требуется в любом случае (преобразование идентичности), int
всюду, поэтому обе функции одинаково хороши. Для второго типа аргумента const char[3]
, а два преобразования:
- для # 1, преобразование матрицы в указатель, преобразование категории lvalue, согласно
[13.3.3.1.1]
; эта категория преобразования игнорируется при сравнении последовательностей преобразования в соответствии с[13.3.3.2]
, так что это в основном то же самое, что и преобразование идентичности для этой цели; - для # 2, параметр имеет ссылочный тип и привязывается непосредственно к аргументу, поэтому, согласно
[13.3.3.1.4]
, это снова преобразование идентичности.
Опять же, не повезло: эти две функции по-прежнему одинаково хороши. Оба являются шаблонами специализации, теперь мы должны увидеть, какой шаблон функции, если таковой имеется, более специализирован ([14.5.6.2]
и [14.8.2.4]
).
ИЗМЕНИТЬ 3: Нижеприведенное описание близко, но не совсем точно. См. Мой ответ за то, что я считаю правильным описанием процесса.
- Вывод аргумента шаблона с №1 в качестве параметра и # 2 в качестве аргумента: мы выставляем значение
M
для заменыN
,T
, выведенного какint
,const char*
, поскольку параметр может быть инициализирован из аргумент типаchar[M]
, все отлично. Насколько я могу судить, # 2 по крайней мере так же специализирован, как # 1 для всех задействованных типов. - Вывод аргумента шаблона С# 2 как параметр и # 1 в качестве аргумента: мы изобретаем тип
U
для заменыT
, параметр типаint
не может быть инициализирован из аргумента типаU
( несвязанные типы) параметр типаchar[N]
не может быть инициализирован из аргумента типаconst char*
, а значение параметра non-typeN
не может быть выведено из аргументов, поэтому... все не удается. Насколько я могу судить, # 1 не является, по крайней мере, таким же специализированным, как # 2 для всех задействованных типов.
РЕДАКТИРОВАТЬ 1: Вышеизложенное было отредактировано на основе комментариев от Columbo и dyp, чтобы отразить тот факт, что ссылки удаляются перед попыткой вывода аргумента шаблона в этом случае.
РЕДАКТИРОВАТЬ 2: На основе информации с hvd также удаляются cv-квалификаторы верхнего уровня. В этом случае это означает, что const char[N]
становится char[N]
, потому что cv-определители на элементах массива также применяются к самому массиву (так как array of const
также является const array
); это вообще не было очевидным в стандарте С++ 11, но было уточнено для С++ 14.
Исходя из вышесказанного, я бы сказал, что частичный порядок шаблонов функций должен выбрать # 2 как более специализированный, и вызов должен решить его без какой-либо двусмысленности.
Теперь вернемся к суровой реальности. Как GCC 4.9.1, так и Clang 3.5.0 со следующими параметрами
-Wall -Wextra -std=c++11 -pedantic
отклонить вызов как неоднозначный, с похожими сообщениями об ошибках. Ошибка от Clang:
prog.cc:16:2: error: call to 'f' is ambiguous
f(7, "ab");
^
prog.cc:4:27: note: candidate function [with T = int]
template<typename T> void f(T, const char*) //#1
^
prog.cc:9:30: note: candidate function [with N = 3]
template<std::size_t N> void f(int, const char(&)[N]) //#2
^
Visual С++ 2013 IntelliSense (на основе компилятора EDG, насколько я знаю) также помещает вызов как неоднозначный. Как ни странно, компилятор VС++ идет вперед и компилирует код без ошибок, выбирая # 2. (Я согласен со мной, поэтому он должен быть прав.)
Очевидный вопрос для экспертов: почему вызов неоднозначен? Что мне не хватает (в области частичного заказа, я бы догадался)?