Перегруженный шаблон непигового типа является неоднозначным, а функция без шаблонов в порядке
Если у нас есть функция шаблона, которая принимает непиковый параметр типа int
или short
, компилятор жалуется на неоднозначность следующего вызова:
// Definition
template <int I> void foo() { std::cout << "I: " << I << '\n'; }
template <short S> void foo() { std::cout << "S: " << S << '\n'; }
// Usage
foo<0>(); // Ambiguous, int or short?
Сначала я не был удивлен этим поведением, буквальный 0
мог быть int
или short
, но если мы попробуем это:
// Definition
void foo(int i) { std::cout << "i: " << i << '\n'; }
void foo(short s) { std::cout << "s: " << s << '\n'; }
// Usage
foo(0); // "i: 0"!
Вызов foo
однозначен! он принимает перегрузку int
(даже если версия шаблона не была). Ну, немного подумав, это не удивительное поведение, ведь нет способа указать литерал short
, поэтому компилятор считает, что 0
- это int
(это поведение по умолчанию AFAIK), в чтобы однозначно вызвать версию short
не templated foo
, мы можем явно создать экземпляр short
:
foo(0); // "i: 0"
foo(short{0}); // "s: 0"
Итак, я думал, что это однозначно определит шаблонную версию, но это не так:
foo<int{0}>(); // Ambiguous call, candidates are int and short versions
foo<short{0}>(); // Ambiguous call, candidates are int and short versions
call of overloaded 'foo()' is ambiguous
foo<int{0}>();
note: candidates are:
void foo() [with int I = 0]
void foo() [with short int S = 0]
call of overloaded 'foo()' is ambiguous
foo<short{0}>();
note: candidates are:
void foo() [with int I = 0]
void foo() [with short int S = 0]
Последнее, что я пробовал, это использовать экземпляры вместо литералов:
template <int I> void foo() { std::cout << "I: " << I << '\n'; }
template <short S> void foo() { std::cout << "S: " << S << '\n'; }
void foo(int i) { std::cout << "i: " << i << '\n'; }
void foo(short s) { std::cout << "s: " << s << '\n'; }
constexpr int i{1};
constexpr short s{5};
int main()
{
foo(i); // "i: 1"
foo(s); // "s: 5"
foo<i>(); // Ambiguous! (expected "I: 1")
foo<s>(); // Ambiguous! (expected "S: 5")
return 0;
}
Без успеха, как вы можете... Итак, какой вопрос?
- Почему вызов templated
foo
неоднозначен? (обратите внимание, что шаблоны foo
не имеют версии int
, поэтому недвусмысленны).
- Почему вызов templated
foo
остается неоднозначным даже после указания типа вызова? (обратите внимание, что шаблонный foo
не работает нормально).
Спасибо.
Ответы
Ответ 1
Вот что происходит, когда вы пишете f<0>()
.
-
Компилятор просматривает f
и находит две объявления шаблонов функций:
template <int I> void foo();
template <short S> void foo();
-
Компилятор видит список аргументов явного шаблона и пытается заменить его на каждое объявление шаблона функции:
template <int I> void foo(); // with I = 0
template <short S> void foo(); // with S = 0
Замена завершается в обоих случаях, потому что 0
является int
и может быть преобразован в short
, и преобразование является разрешенным преобразованием в этом контексте.
-
После подстановки создаются две специализированные специализации. Оба они жизнеспособны. Затем выполняется разрешение перегрузки - и поскольку подпись идентична и не применяется тай-брейк, разрешение перегрузки выходит из строя, а вызов неоднозначен.
Дело здесь в том, что нормальные правила разрешения перегрузки не применяются к аргументам шаблона. Преобразования для аргументов шаблона применяются на более ранней стадии, прежде чем произойдет регулярное разрешение перегрузки.
Ответ 2
Сообщение об ошибке компилятора *. Вы бы интуитивно считали, что это означает, что "вызов функции неоднозначен!", Но на самом деле компиляция не выполняется на более ранней стадии, определение специализированной функции даже не генерируется в этой точке.
На самом деле это означает: "Специализация функции неоднозначна!"
Давайте посмотрим, как это компилируется:
template <short S> void foo() { std::cout << "S: " << S << '\n'; }
int main(int argc, char* argv[])
{
foo<0>();
return 0;
}
Первым шагом компиляции является специализированная специализация.
Шаг 1. Компилятор понимает, что foo<0>
является специализированным шаблоном и соответственно генерирует объявление функции:
template <short S> void foo() { std::cout << "S: " << S << '\n'; }
template<>
void foo<0>();
int main(int argc, char* argv[])
{
foo<0>();
return 0;
}
Шаг 2. Компилятор переопределяет функцию, которая фактически вызывается (это кажется очевидным в этом случае, но это менее очевидно, если у вас есть шаблон шаблона.) и генерирует определение:
template <short S> void foo() { std::cout << "S: " << S << '\n'; }
template<>
void foo<0>();
int main(int argc, char* argv[])
{
foo<0>();
return 0;
}
template<>
void foo<0>(){
std::cout << "S: " << 0 << '\n';
}
Шаг 3: Теперь у вас есть вызываемая функция, компиляция продолжается нормально.
Давайте попробуем выполнить те же шаги в вашем случае:
template <short S> void foo() { std::cout << "S: " << S << '\n'; }
template <int I> void foo() { std::cout << "I: " << I << '\n'; }
int main(int argc, char* argv[])
{
foo<0>();
return 0;
}
Шаг 1: создание объявления функции:
template <short S> void foo() { std::cout << "S: " << S << '\n'; }
template <int I> void foo() { std::cout << "I: " << I << '\n'; }
template<>
void foo<0>();
int main(int argc, char* argv[])
{
foo<0>();
return 0;
}
И на данный момент компиляция терпит неудачу, потому что специализированное объявление foo неоднозначно.
Если вы хотите получить доказательство, попробуйте выполнить компиляцию этого кода:
template <short S> void foo() { std::cout << "S: " << S << '\n'; }
template <int I> void foo() { std::cout << "I: " << I << '\n'; }
template<>
void foo<0>();
int main(int argc, char* argv[])
{
return 0;
}
Вы получите то же сообщение об ошибке без вызова функции!
Обновление
Таким образом, выгода заключается в том, что все переводится в специализированные объявления функций. Поэтому независимо от того, пишете ли вы foo<int{0}>
из foo<short{0}>
, компилятор будет генерировать template<> void foo<0>();
для обоих. Явные типы будут проигнорированы. (Вот почему его действительно важно, чтобы они были constexpr
-s.)
Обновление
Как T.C. в своем комментарии, в стандарте (413-я страница в формате PDF) есть очень похожий пример:
[Пример: В следующем примере предполагается, что подписанный char не может представляют значение 1000, сужение преобразования (8.5.4) будет требуется преобразовать шаблон-аргумент типа int в подписанный char, поэтому замена для второго шаблона (14.3.2) не выполняется.
template <int> int f(int);
template <signed char> int f(int);
int i1 = f<1000>(0); // OK
int i2 = f<1>(0); // ambiguous; not narrowing
-end пример]
* Сообщение об ошибке полностью верно. Не может быть особенно интуитивным, но он отражает процедуру, указанную в стандарте. - T.C.
Ответ 3
Функция шаблона здесь параметризована по значению и только по значению, а не по типу! обновление: теперь я не уверен.
С другой стороны, не templated версия параметризуется по типу (и можно пользоваться полиморфными вызовами).
обновление:
Ну, похоже, что запущенные функции искаженное имя фактически зависит от типа числового параметра шаблона.