ОК, начнем с самого начала. «Template-name» шаблона - это фактическое имя функции или класса, который подвергается шаблону; в
template<class T> T foo(T t);
foo
- это имя шаблона. Для шаблонов функций правило определения того, являются ли они одинаковыми, довольно длинное, описанное в 14.5.5.1 «Перегрузка шаблона функции». Параграф 6 этого раздела (здесь я цитирую C ++ 03, так что формулировки и номера абзацев могли измениться в C ++ 11) определяют термины эквивалент и функционально эквивалентные , когда применяется к выражениям, включающим параметры шаблона.
Короче говоря, эквивалентные выражения одинаковы, за исключением возможного использования разных имен для параметров шаблона, а функционально эквивалентные выражения одинаковы, если они оказываются одинаковыми. Например, первые два объявления f
эквивалентны , но третье только функционально эквивалентно другим двум: -
template<int A, int B>
void f(array<A + B>);
template<int T1, int T2>
void f(array<T1 + T2>);
template<int A, int B>
void f(array< mpl::plus< mpl::int<A>, mpl::int<B> >::value >);
В параграфе 7 продолжается расширение этих двух определений на целые шаблоны функций. Два соответствующих функциональных шаблона (в именах, области видимости и списках параметров шаблона) эквивалентны, если они также имеют эквивалентные типы возвращаемых данных и типы аргументов, или функционально эквивалентны, если они имеют только функционально эквивалентные возвращаемые типы и типы аргументов. Глядя на ваш второй пример, эти две функции только функционально эквивалентны: -
template<typename T>
T foo(T t);
template<typename T>
typename identity<T>::type foo(T t);
Пункт 7 завершается страшным предупреждением о том, что «если программа содержит объявления шаблонов функций, которые являются функционально эквивалентными, но не эквивалентными, программа является некорректной; диагностика не требуется». Ваш второй пример, таким образом, не является допустимым C ++. Подобное обнаружение ошибок потребовало бы, чтобы каждое объявление и определение шаблона функции были аннотированы в двоичном файле с помощью AST, описывающей выражение шаблона для каждого параметра и типа возвращаемого значения, поэтому стандарт не требует реализации для его обнаружения. MSVC оправдывает компиляцию вашего третьего примера так, как вы хотели, но было бы так же оправдано нарушать.
Переходя к явной реализации, важным разделом является 14.7, «Создание и специализация шаблона». Пункт 5 запрещает все следующее:
- Явное создание экземпляра шаблона более одного раза;
- Явное создание экземпляров и специализация одного и того же шаблона;
- Явная специализация шаблона для одного и того же набора аргументов более одного раза.
Опять же, «диагностика не требуется», так как ее довольно сложно обнаружить.
Итак, чтобы расширить ваш пример явного создания экземпляра, следующий код нарушает второе правило и является недопустимым: -
/* Template definition. */
template<typename T>
T foo(T t)
{ ... }
/* Specialization, OK in itself. */
template< >
int foo(int t)
{ ... }
/* Explicit instantiation, OK in itself. */
template< >
int foo(int t);
Это недопустимо вне зависимости от местоположения явной специализации и явного создания экземпляра, но, конечно, поскольку не требуется никакой диагностики, вы можете получить полезные результаты на некоторых компиляторах. Обратите внимание также на разницу между явной реализацией и явной специализацией. Следующий пример некорректен, потому что объявляет явную специализацию, не определяя ее: -
template<typename T>
T f(T f)
{ ... }
template< >
int f(int);
void g(void)
{ f(3); }
но этот пример правильно сформирован, потому что у него есть явный экземпляр: -
template<typename T>
T f(T f)
{ ... }
template f(int);
void g(void)
{ f(3); }
< >
имеет все значение. Также предупреждаем, что даже когда вы определяете явную специализацию, она должна быть до того, как вы ее используете , иначе компилятор мог бы уже сгенерировать неявную реализацию для этого шаблона. Это в параграфе 6 «Явная специализация» 14.7.3, чуть ниже того места, где вы читали, и опять же, диагностика не требуется. Чтобы адаптировать тот же пример, это плохо сформировано: -
template<typename T>
T f(T f)
{ ... }
void g(void)
{ f(3); } // Implicitly specializes int f(int)
template< >
int f(int) // Too late for an explicit specialization
{ ... }
Если вы еще не достаточно смущены, взгляните на свой последний пример: -
template<typename T>
T foo(T t) { ... }
template<typename T>
int foo(int t) { ... }
Второе определение foo
является , а не специализацией первого определения.Это должно быть template< > int foo(int)
, чтобы быть специализацией template<typename T> T foo(T)
.Но это нормально: перегрузка функций разрешена и разрешена между шаблонами функций и обычными функциями.Вызовы формы foo(3)
всегда будут использовать первое определение, потому что его параметр шаблона T
может быть выведен из типа аргумента.Второе определение не позволяет выводить его параметр шаблона из типа аргумента.Только явно указав T
, вы можете получить второе определение, и только тогда, когда вызов не является неоднозначным с первым определением: -
f<int>(3); // ambiguous
f<string>(3); // can only be the second one
Весь процесс выполнения разрешения перегрузки для шаблонов функций слишкомдолго описывать тут.Прочтите раздел 14.8.3, если вам интересно, и задайте больше вопросов: -)