Я испытываю поведение разрешения перегрузки, которое кажется очень неожиданным. Следующий код отклонен с ошибкой неоднозначности как gcc, так и clang:
template <typename T>
struct A
{
typedef T key_type;
};
template <typename T>
void foo(A<T> rng, T val);
template <typename T, typename U = T>
void foo(T, typename U::key_type);
int main()
{
A<int> i;
foo(i, 0);
}
Ошибка:
test.cpp:16:5: error: call to 'foo' is ambiguous
foo(i, 0);
^~~
test.cpp:8:6: note: candidate function [with T = int]
void foo(A<T> rng, T val);
^
test.cpp:11:6: note: candidate function [with T = A<int>, U = A<int>]
void foo(T, typename U::key_type);
^
Я ожидал бы, что оба будут точными совпадениями, но первая перегрузка выиграет в частичном упорядочении, так как в первом параметре A<T>
более специализированный, чем T
,
Что поражает, так это то, что если я изменю вторую подпись на:
template <typename T, typename U = T>
void foo(T, typename T::key_type);
и gcc, и clang теперь принимают код и выбирают первую перегрузку, как я и ожидал.
Я не понимаю, как это изменение может повлиять на поведение: все, что я сделал, это заменил использование параметра шаблона, который не был ни явно указан, ни выведен (U
) со значением по умолчанию (T
).
С другой стороны, поведение до изменения неожиданно для начала, поэтому, возможно, я что-то упускаю.
Может ли кто-нибудь объяснить:
В случае, если это уместно, я проверял версии компилятора gcc 4.8.0 и последнюю сборку ствола clang.
Вопрос в том, есть ли фаза замены выведенных аргументов шаблона в список параметров после вывода аргумента. На этом этапе аргументы по умолчанию будут использоваться для параметров шаблона, которые еще не были выведены.
Вопрос о том, в каких контекстах удержания выполняется этот дополнительный шаг, а о том, что не является предметом активной основной проблемы, http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#697 .
Если вы выполняете этот дополнительный шаг замещения, вам также необходимо создать экземпляры шаблонов (в противном случае шаг замещения не будет иметь особого смысла). Вы также можете просто выбрать аргументы по умолчанию, не делая подстановку, но в Стандарте эти две вещи идут вместе, так что как разработчик я бы не выбрал этот путь.
Частичное упорядочение в значительной степени не зависит от контекста, для которого выполняется частичное упорядочение (учитываются некоторые зависящие от контекста вещи — например, параметры функции, которые не имеют явных аргументов вызова, игнорируются). Он также не зависит от того, передан ли для параметра шаблона явный аргумент шаблона или нет (поэтому, если вы задали значение для U
Частичное упорядочение не запомнило бы это.
Clang и GCC не выполняют шаг замещения и не используют аргументы шаблона по умолчанию. Итак, когда T
сравнивается с U::key_type
выяснить U
они говорят: «Да, не выводимый контекст. Мы скажем:« Успех, нечего не соответствовать! » для этого параметра «. То же самое происходит, когда он сравнивает T
против T::key_type
, Когда он сравнивает другое направление, WhatEver::key_type
против T
, T
тоже может вывести этот зависимый тип. Так что для второго параметра в обеих ваших попытках оба шаблона, по крайней мере, так же специализированы, как и другие.
Однако важным отличием является то, что после вычета должны быть значения для всех параметров, используемых в списке типов параметров.
В большинстве случаев все параметры шаблона должны иметь значения, чтобы вывод был успешным, но для целей частичного упорядочения параметр шаблона может оставаться без значения при условии, что он не используется в типах, используемых для частичного упорядочения. [Примечание. Параметр шаблона, используемый в невзаимодействующем контексте, считается использованным. — конец примечания]
Во второй попытке, T
был выведен по первому параметру, поэтому ничего плохого не произошло после сравнения типов параметров / аргументов. С первой попытки, U
был оставлен без ответа, и поэтому первый шаблон не считался «более специализированным», чем второй в последствии.
Других решений пока нет …