Namespaces
Variants

Function template

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Class template
Function template
Miscellaneous

Шаблон функции определяет семейство функций.

Содержание

Синтаксис

template < parameter-list > function-declaration (1)
template < parameter-list > requires constraint function-declaration (2) (since C++20)
function-declaration-with-placeholders (3) (since C++20)
export template < parameter-list > function-declaration (4) (удалено в C++11)

Объяснение

parameter-list - непустой разделённый запятыми список template parameters , каждый из которых является либо constant parameter , либо type parameter , либо template parameter , либо parameter pack любого из этих типов (начиная с C++11) . Как и в любом шаблоне, параметры могут быть constrained (начиная с C++20)
function-declaration - function declaration . Объявленное имя функции становится именем шаблона.
constraint - constraint expression , которое ограничивает template parameters, принимаемые этим шаблоном функции
function-declaration-
with-placeholders
- function declaration , в котором тип по крайней мере одного параметра использует placeholder auto или Concept auto : список template parameters будет содержать один изобретённый параметр для каждого placeholder (см. сокращённые шаблоны функций ниже)

export был необязательным модификатором, который объявлял шаблон как экспортированный (при использовании с шаблоном класса, он также объявлял все его члены экспортированными). Файлы, инстанцирующие экспортированные шаблоны, не нуждались в включении их определений: объявления было достаточно. Реализации export были редкими и расходились в деталях между собой.

(до C++11)

Сокращённый шаблон функции

Когда типы-заполнители (либо auto , либо Concept auto ) появляются в списке параметров объявления функции или объявления шаблона функции, такое объявление объявляет шаблон функции, и для каждого заполнителя в список параметров шаблона добавляется один сконструированный параметр шаблона:

void f1(auto); // same as template<class T> void f1(T)
void f2(C1 auto); // same as template<C1 T> void f2(T), if C1 is a concept
void f3(C2 auto...); // same as template<C2... Ts> void f3(Ts...), if C2 is a concept
void f4(const C3 auto*, C4 auto&); // same as template<C3 T, C4 U> void f4(const T*, U&);
template<class T, C U>
void g(T x, U y, C auto z); // same as template<class T, C U, C W> void g(T x, U y, W z);

Сокращённые шаблоны функций могут быть специализированы как и все шаблоны функций.

template<>
void f4<int>(const int*, const double&); // specialization of f4<int, const double>


(начиная с C++20)

Сигнатура шаблона функции

Каждая шаблонная функция имеет сигнатуру.

Сигнатура заголовка шаблона — это список параметров шаблона , исключая имена параметров шаблона и аргументы по умолчанию , и requires-клаузу (если присутствует) (начиная с C++20) .

Сигнатура шаблона функции включает имя, список параметров, возвращаемый тип , завершающее условие requires (если присутствует) (начиная с C++20) и сигнатуру template-head . За исключением следующих случаев, её сигнатура также включает охватывающее пространство имён.

Если шаблон функции является членом класса, его сигнатура содержит класс, членом которого является функция, вместо включающего пространства имён. Его сигнатура также содержит завершающее условие requires (если есть) (начиная с C++20) , ref-квалификатор (если есть), и (начиная с C++11) cv -квалификаторы (если есть).

Если шаблон функции является friend с ограничением, включающим параметры охватывающего шаблона, его сигнатура содержит охватывающий класс вместо охватывающего пространства имен.

(since C++20)

Инстанцирование шаблона функции

Шаблон функции сам по себе не является типом или функцией. Никакой код не генерируется из исходного файла, содержащего только определения шаблонов. Чтобы появился какой-либо код, шаблон должен быть инстанцирован: аргументы шаблона должны быть определены, чтобы компилятор мог сгенерировать реальную функцию (или класс, из шаблона класса).

Явное инстанцирование

template return-type name < argument-list > ( parameter-list ) ; (1)
template return-type name ( parameter-list ) ; (2)
extern template return-type name < argument-list > ( parameter-list ) ; (3) (начиная с C++11)
extern template return-type name ( parameter-list ) ; (4) (начиная с C++11)
1) Явное определение инстанцирования (без template argument deduction если каждый не-умолчательный параметр шаблона явно указан)
2) Явное определение инстанцирования с выводом аргументов шаблона для всех параметров
3) Явное объявление инстанцирования (без вывода аргументов шаблона, если каждый не-умолчательный параметр шаблона явно указан)
4) Явное объявление инстанцирования с выводом аргументов шаблона для всех параметров

Явное определение инстанцирования принудительно создает экземпляр функции или функции-члена, на которые оно ссылается. Оно может появиться в программе в любом месте после определения шаблона, и для данного списка аргументов разрешено появляться в программе только один раз, диагностика не требуется.

Декларация явной инстанциации (extern template) предотвращает неявные инстанциации: код, который в противном случае вызвал бы неявную инстанциацию, должен использовать определение явной инстанциации, предоставленное в другом месте программы.

(since C++11)

Завершающий шаблонный-аргумент может быть опущен в явном инстанцировании специализации шаблона функции или специализации шаблона функции-члена, если он может быть выведен из параметра функции:

template<typename T>
void f(T s)
{
    std::cout << s << '\n';
}
template void f<double>(double); // создает экземпляр f<double>(double)
template void f<>(char);         // создает экземпляр f<char>(char), аргумент шаблона выведен
template void f(int);            // создает экземпляр f<int>(int), аргумент шаблона выведен

Явное инстанцирование шаблона функции или элемента-функции шаблона класса не может использовать inline или constexpr . Если объявление явного инстанцирования ссылается на неявно объявленную специальную функцию-член, программа является некорректной.

Явное инстанцирование конструктора не может использовать список параметров шаблона (синтаксис (1) ), что также никогда не требуется, поскольку они могут быть выведены (синтаксис (2) ).

Явное инстанцирование перспективного деструктора должно указывать на выбранный деструктор класса.

(since C++20)

Явные объявления инстанцирования не подавляют неявное инстанцирование inline -функций, auto -объявлений, ссылок и специализаций шаблонов классов. (таким образом, когда inline-функция, являющаяся объектом явного объявления инстанцирования, ODR-используется, она неявно инстанцируется для подстановки, но её внешняя копия не генерируется в данной единице трансляции)

Явное определение инстанцирования шаблона функции с аргументами по умолчанию не является использованием аргументов и не пытается их инициализировать:

char* p = 0;
template<class T>
T g(T x = &p) { return x; }
template int g<int>(int); // OK, несмотря на то что &p не является int.

Неявная инстанциация

Когда код ссылается на функцию в контексте, требующем наличия определения функции , или если существование определения влияет на семантику программы (начиная с C++11) , и данная функция не была явно инстанцирована, происходит неявное инстанцирование. Список шаблонных аргументов не обязательно указывать, если он может быть выведен из контекста.

#include <iostream>
template<typename T>
void f(T s)
{
    std::cout << s << '\n';
}
int main()
{
    f<double>(1); // создает экземпляр и вызывает f<double>(double)
    f<>('a');     // создает экземпляр и вызывает f<char>(char)
    f(7);         // создает экземпляр и вызывает f<int>(int)
    void (*pf)(std::string) = f; // создает экземпляр f<string>(string)
    pf("∇");                     // вызывает f<string>(string)
}

Существование определения функции считается влияющим на семантику программы, если функция требуется для константной оценки выражением, даже если константная оценка выражения не требуется или если оценка константного выражения не использует определение.

template<typename T>
constexpr int f() { return T::value; }
template<bool B, typename T>
void g(decltype(B ? f<T>() : 0));
template<bool B, typename T>
void g(...);
template<bool B, typename T>
void h(decltype(int{B ? f<T>() : 0}));
template<bool B, typename T>
void h(...);
void x()
{
    g<false, int>(0); // OK: B ? f<T>() : 0 is not potentially constant evaluated
    h<false, int>(0); // error: instantiates f<int> even though B evaluates to false
                      // and list-initialization of int from int cannot be narrowing
}
(начиная с C++11)

Примечание: полное опускание <> позволяет разрешению перегрузки рассматривать как шаблонные, так и нешаблонные перегрузки.

Вывод аргументов шаблона

Для создания экземпляра шаблона функции каждый параметр шаблона должен быть известен, но не каждый параметр шаблона должен быть указан явно. Когда это возможно, компилятор выводит недостающие параметры шаблона из аргументов функции. Это происходит при попытке вызова функции и при взятии адреса шаблона функции.

template<typename To, typename From>
To convert(From f);
void g(double d) 
{
    int i = convert<int>(d);    // вызывает convert<int,double>(double)
    char c = convert<char>(d);  // вызывает convert<char,double>(double)
    int(*ptr)(float) = convert; // инстанцирует convert<int, float>(float)
}

Этот механизм позволяет использовать шаблонные операторы, поскольку не существует синтаксиса для указания шаблонных аргументов для оператора, кроме как переписав его в виде выражения вызова функции.

#include <iostream>
int main() 
{
    std::cout << "Hello, world" << std::endl;
    // operator<< ищется через ADL как std::operator<<,
    // затем выводится как operator<<<char, std::char_traits<char>> оба раза
    // std::endl выводится как &std::endl<char, std::char_traits<char>>
}

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

См. вывод аргументов шаблона для подробностей.

Явные аргументы шаблона

Аргументы шаблона шаблона функции могут быть получены из

  • вывод аргументов шаблона
  • аргументы шаблона по умолчанию
  • указаны явно, что может быть сделано в следующих контекстах:
  • в выражении вызова функции
  • при взятии адреса функции
  • при инициализации ссылки на функцию
  • при формировании указателя на метод класса
  • в явной специализации
  • в явном инстанцировании
  • в объявлении дружественной функции

Невозможно явно указать аргументы шаблона для перегруженных операторов , функций преобразования и конструкторов, поскольку они вызываются без использования имени функции.

Указанные аргументы шаблона должны соответствовать параметрам шаблона по типу (т.е. тип для типа, константа для константы, шаблон для шаблона). Не может быть больше аргументов, чем параметров (если только один параметр не является пачкой параметров, в этом случае должен быть аргумент для каждого непакетного параметра) (since C++11) .

Указанные константные аргументы должны либо соответствовать типам соответствующих константных параметров шаблона, либо быть преобразуемыми к ним .

Параметры функции, которые не участвуют в выводе аргументов шаблона (например, если соответствующие аргументы шаблона явно указаны), подвергаются неявным преобразованиям к типу соответствующего параметра функции (как в обычном разрешении перегрузки ).

Явно указанный пакет параметров шаблона может быть расширен с помощью вывода аргументов шаблона, если имеются дополнительные аргументы:

template<class... Types>
void f(Types... values);
void g()
{
    f<int*, float*>(0, 0, 0); // Types = {int*, float*, int}
}
(since C++11)

Подстановка аргументов шаблона

Когда все аргументы шаблона были указаны, выведены или получены из аргументов шаблона по умолчанию, каждое использование параметра шаблона в списке параметров функции заменяется соответствующими аргументами шаблона.

Сбой подстановки (то есть неудача при замене параметров шаблона выведенными или предоставленными аргументами шаблона) для шаблона функции исключает этот шаблон функции из набора перегрузок . Это позволяет различными способами манипулировать наборами перегрузок с использованием метапрограммирования шаблонов: подробности см. в SFINAE .

После подстановки все параметры функций типа массив и функция преобразуются в указатели, а все топ-level cv-квалификаторы удаляются из параметров функций (как в обычном объявлении функции ).

Удаление cv-квалификаторов верхнего уровня не влияет на тип параметра, как он представлен внутри функции:

template<class T>
void f(T t);
template<class X>
void g(const X x);
template<class Z>
void h(Z z, Z* zp);
// две разные функции с одинаковым типом, но 
// внутри функции t имеет разные cv-квалификаторы
f<int>(1);       // тип функции void(int), t имеет тип int
f<const int>(1); // тип функции void(int), t имеет тип const int
// две разные функции с одинаковым типом и одинаковым x
// (указатели на эти две функции не равны,
//  и статические переменные внутри функций имели бы разные адреса)
g<int>(1);       // тип функции void(int), x имеет тип const int
g<const int>(1); // тип функции void(int), x имеет тип const int
// отбрасываются только cv-квалификаторы верхнего уровня:
h<const int>(1, NULL); // тип функции void(int, const int*) 
                       // z имеет тип const int, zp имеет тип const int*

Перегрузка шаблонов функций

Шаблоны функций и нешаблонные функции могут быть перегружены.

Нетеплейтная функция всегда отличается от специализации шаблона с тем же типом. Специализации разных шаблонов функций всегда отличаются друг от друга, даже если они имеют одинаковый тип. Два шаблона функций с одинаковым возвращаемым типом и одинаковым списком параметров являются различными и могут быть различимы по их явному списку аргументов шаблона.

Когда выражение, использующее параметры шаблона типа или константы, появляется в списке параметров функции или в типе возвращаемого значения, это выражение остается частью сигнатуры шаблона функции для целей перегрузки:

template<int I, int J>
A<I+J> f(A<I>, A<J>); // перегрузка #1
template<int K, int L>
A<K+L> f(A<K>, A<L>); // идентично #1
template<int I, int J>
A<I-J> f(A<I>, A<J>); // перегрузка #2

Два выражения, включающие параметры шаблона, называются эквивалентными , если два определения функций, содержащих эти выражения, были бы одинаковыми согласно ODR , то есть два выражения содержат одинаковую последовательность токенов, имена которых разрешаются в те же сущности через поиск имен, за исключением того, что параметры шаблона могут быть по-разному названы. Два лямбда-выражения никогда не являются эквивалентными. (начиная с C++20)

template<int I, int J>
void f(A<I+J>); // перегрузка шаблона #1
template<int K, int L>
void f(A<K+L>); // эквивалентно #1

При определении эквивалентности двух зависимых выражений рассматриваются только задействованные зависимые имена, а не результаты поиска имен. Если множественные объявления одного шаблона различаются результатом поиска имен, используется первое такое объявление:

template<class T>
decltype(g(T())) h(); // decltype(g(T())) является зависимым типом
int g(int);
template<class T>
decltype(g(T())) h()
{                  // повторное объявление h() использует ранее выполненный поиск
    return g(T()); // хотя поиск здесь находит g(int)
}
int i = h<int>(); // подстановка аргументов шаблона завершается неудачей; g(int)
                  // не была в области видимости при первом объявлении h()

Два шаблона функций считаются эквивалентными если

  • они объявлены в одной области видимости
  • они имеют одинаковое имя
  • они имеют эквивалентные списки параметров шаблона, что означает одинаковую длину списков, и для каждой соответствующей пары параметров верно всё следующее:
  • два параметра относятся к одному виду (оба являются типами, обе константами или оба шаблонами)
  • они либо оба являются паками параметров, либо ни один из них
(since C++11)
  • если константы, их типы эквивалентны,
  • если шаблоны, их параметры шаблонов эквивалентны,
  • если один объявлен с именем концепции, то оба объявлены, и имена концепций эквивалентны.
(начиная с C++20)
  • выражения, включающие параметры шаблона в их возвращаемых типах и списках параметров, являются эквивалентными
  • выражения в их requires-клаузах, следующие за списками параметров шаблона, если они присутствуют, эквивалентны
  • выражения в их requires-клаузах, следующие за деклараторами функций, если они присутствуют, эквивалентны
(since C++20)

Два потенциально вычисляемых (since C++20) выражения, содержащих параметры шаблона, называются функционально эквивалентными , если они не являются эквивалентными , но для любого заданного набора аргументов шаблона вычисление обоих выражений даёт одинаковое значение.

Два шаблона функций считаются функционально эквивалентными , если они эквивалентны , за исключением того, что одно или несколько выражений, включающих параметры шаблонов в их возвращаемых типах и списках параметров, являются функционально эквивалентными .

Кроме того, две шаблонные функции являются функционально эквивалентными , но не эквивалентными , если их ограничения заданы по-разному, но они принимают и удовлетворяются одним и тем же набором списков аргументов шаблона.

(since C++20)

Если программа содержит объявления шаблонов функций, которые являются функционально эквивалентными , но не эквивалентными , программа является некорректной; диагностическое сообщение не требуется.

// эквивалентны
template<int I>
void f(A<I>, A<I+10>); // перегрузка #1
template<int I>
void f(A<I>, A<I+10>); // переобъявление перегрузки #1
// не эквивалентны
template<int I>
void f(A<I>, A<I+10>); // перегрузка #1
template<int I>
void f(A<I>, A<I+11>); // перегрузка #2
// функционально эквивалентны, но не эквивалентны
// Программа некорректна, диагностика не требуется
template<int I>
void f(A<I>, A<I+10>);      // перегрузка #1
template<int I>
void f(A<I>, A<I+1+2+3+4>); // функционально эквивалентна

Когда одна и та же специализация шаблона функции соответствует более чем одной перегруженной шаблоне функции (это часто происходит в результате вывода аргументов шаблона ), частичное упорядочивание перегруженных шаблонов функций выполняется для выбора наилучшего соответствия.

В частности, частичное упорядочивание происходит в следующих ситуациях:

1) разрешение перегрузки для вызова специализации шаблона функции:
template<class X>
void f(X a);
template<class X>
void f(X* a);
int* p;
f(p);
2) когда берется адрес специализации шаблона функции :
template<class X>
void f(X a);
template<class X>
void f(X* a);
void (*p)(int*) = &f;
3) когда для соответствия оператору placement operator new выбирается специализация шаблона функции placement operator delete:
4) когда объявление friend-функции , явное инстанцирование или явная специализация ссылается на специализацию шаблона функции:
template<class X>
void f(X a);  // first template f
template<class X>
void f(X* a); // second template f
template<>
void f<>(int *a) {} // explicit specialization
// template argument deduction comes up with two candidates:
// f<int*>(int*) and f<int>(int*)
// partial ordering selects f<int>(int*) as more specialized

Неформально "A является более специализированным, чем B" означает "A принимает меньше типов, чем B".

Формально, чтобы определить, какой из двух шаблонов функций является более специализированным, процесс частичного упорядочивания сначала преобразует один из двух шаблонов следующим образом:

  • Для каждого типа, константы и параметра шаблона, включая пачки параметров, (начиная с C++11) генерируется уникальный фиктивный тип, значение или шаблон и подставляется в тип функции шаблона
  • Если только один из двух сравниваемых шаблонов функций является функцией-членом, и этот шаблон функции является нестатическим членом некоторого класса A , в его список параметров вставляется новый первый параметр. Пусть cv будет cv-квалификаторами шаблона функции и ref будет ref-квалификатором шаблона функции (начиная с C++11) , тогда новый тип параметра будет cv A& если только ref не является && , или ref отсутствует и первый параметр другого шаблона имеет тип rvalue-ссылки, в этом случае тип будет cv A&& (начиная с C++11) . Это помогает упорядочиванию операторов, которые могут быть найдены как функции-члены и как обычные функции:
struct A {};
template<class T>
struct B
{
    template<class R>
    int operator*(R&); // #1
};
template<class T, class R>
int operator*(T&, R&); // #2
int main()
{
    A a;
    B<A> b;
    b * a; // вывод аргументов шаблона для int B<A>::operator*(R&) дает R=A
           //                             для int operator*(T&, R&), T=B<A>, R=A
    // Для частичного упорядочивания шаблон функции-члена B<A>::operator*
    // преобразуется в template<class R> int operator*(B<A>&, R&);
    // частичное упорядочивание между
    //     int operator*(   T&, R&)  T=B<A>, R=A
    // и int operator*(B<A>&, R&)  R=A
    // выбирает int operator*(B<A>&, A&) как более специализированную
}

После того как один из двух шаблонов был преобразован, как описано выше, template argument deduction выполняется с использованием преобразованного шаблона в качестве аргументного шаблона и исходного типа шаблона другого шаблона в качестве параметрного шаблона. Затем процесс повторяется с использованием второго шаблона (после преобразований) в качестве аргумента и первого шаблона в его исходной форме в качестве параметра.

Типы, используемые для определения порядка, зависят от контекста:

  • в контексте вызова функции, типами являются те типы параметров функции, для которых в вызове функции есть аргументы (аргументы по умолчанию, packs параметров, (since C++11) и параметры с многоточием не учитываются -- см. примеры ниже)
  • в контексте вызова пользовательской функции преобразования, используются возвращаемые типы шаблонов функций преобразования
  • в других контекстах используется тип шаблона функции

Каждый тип из приведенного выше списка из параметра шаблона выводится. Перед началом вывода каждый параметр P параметра шаблона и соответствующий аргумент A аргумента шаблона корректируется следующим образом:

  • Если оба P и A являются ссылочными типами, определить, какой из них имеет больше cv-квалификаторов (во всех остальных случаях cv-квалификаторы игнорируются для целей частичного упорядочивания)
  • Если P является ссылочным типом, он заменяется на тип, на который ссылается
  • Если A является ссылочным типом, он заменяется на тип, на который ссылается
  • Если P имеет cv-квалификаторы, P заменяется на версию без cv-квалификаторов
  • Если A имеет cv-квалификаторы, A заменяется на версию без cv-квалификаторов

После этих корректировок, вывод P из A выполняется в соответствии с выводом аргументов шаблона из типа .

Если P является пачкой параметров функции, тип A каждого оставшегося параметра шаблона аргумента сравнивается с типом P декларатора пачки параметров функции. Каждое сравнение выводит аргументы шаблона для последующих позиций в пачках параметров шаблона, раскрываемых пачкой параметров функции.

Если A был преобразован из пачки параметров функции, он сравнивается с каждым оставшимся параметром шаблона параметра.

(начиная с C++11)

Если аргумент A преобразованного шаблона-1 может быть использован для выведения соответствующего параметра P шаблона-2, но не наоборот, тогда этот A является более специализированным, чем P в отношении типа(ов), которые выводятся этой P/A парой.

Если дедукция успешна в обоих направлениях, и исходные P и A были ссылочными типами, то выполняются дополнительные проверки:

  • Если A был lvalue-ссылкой, а P был rvalue-ссылкой, A считается более специализированным, чем P
  • Если A был более cv-квалифицирован, чем P , A считается более специализированным, чем P

Во всех остальных случаях ни один шаблон не является более специализированным, чем другой, в отношении типа (типов), выводимого этой P/A парой.

После рассмотрения каждого P и A в обоих направлениях, если для каждого типа, который был рассмотрен,

  • шаблон-1 является по меньшей мере таким же специализированным, как шаблон-2 для всех типов
  • шаблон-1 является более специализированным, чем шаблон-2 для некоторых типов
  • шаблон-2 не является более специализированным, чем шаблон-1 для любых типов ИЛИ не является по меньшей мере таким же специализированным для любых типов

Тогда шаблон-1 является более специализированным, чем шаблон-2. Если приведенные выше условия истинны после перестановки порядка шаблонов, тогда шаблон-2 является более специализированным, чем шаблон-1. В противном случае ни один из шаблонов не является более специализированным, чем другой.

В случае равенства, если у одного шаблона функции есть завершающий пакет параметров, а у другого нет, тот, у которого параметр опущен, считается более специализированным, чем тот, у которого есть пустой пакет параметров.

(since C++11)

Если после рассмотрения всех пар перегруженных шаблонов существует один, который однозначно более специализирован, чем все остальные, выбирается специализация этого шаблона, в противном случае компиляция завершается неудачно.

В следующих примерах фиктивные аргументы будут называться U1, U2:

template<class T>
void f(T);        // шаблон #1
template<class T>
void f(T*);       // шаблон #2
template<class T>
void f(const T*); // шаблон #3
void m()
{
    const int* p;
    f(p); // разрешение перегрузки выбирает: #1: void f(T ) [T = const int *]
          //                            #2: void f(T*) [T = const int]
          //                            #3: void f(const T *) [T = int]
    // частичное упорядочивание:
    // #1 из преобразованного #2: void(T) из void(U1*): P=T A=U1*: вывод успешен: T=U1*
    // #2 из преобразованного #1: void(T*) из void(U1): P=T* A=U1: вывод неудачен
    // #2 более специализирован, чем #1 относительно T
    // #1 из преобразованного #3: void(T) из void(const U1*): P=T, A=const U1*: успешно
    // #3 из преобразованного #1: void(const T*) из void(U1): P=const T*, A=U1: неудачно
    // #3 более специализирован, чем #1 относительно T
    // #2 из преобразованного #3: void(T*) из void(const U1*): P=T* A=const U1*: успешно
    // #3 из преобразованного #2: void(const T*) из void(U1*): P=const T* A=U1*: неудачно
    // #3 более специализирован, чем #2 относительно T
    // результат: выбран #3
    // другими словами, f(const T*) более специализирован, чем f(T) или f(T*)
}
template<class T>
void f(T, T*);   // #1
template<class T>
void f(T, int*); // #2
void m(int* p)
{
    f(0, p); // вывод для #1: void f(T, T*) [T = int]
             // вывод для #2: void f(T, int*) [T = int]
    // частичное упорядочивание:
    // #1 из #2: void(T,T*) из void(U1,int*): P1=T, A1=U1: T=U1
    //                                            P2=T*, A2=int*: T=int: неудача
    // #2 из #1: void(T,int*) из void(U1,U2*): P1=T A1=U1: T=U1
    //                                             P2=int* A2=U2*: неудача
    // ни одна не является более специализированной относительно T, вызов неоднозначен
}
template<class T>
void g(T);  // шаблон #1
template<class T>
void g(T&); // шаблон #2
void m()
{
    float x;
    g(x); // вывод из #1: void g(T ) [T = float]
          // вывод из #2: void g(T&) [T = float]
    // частичное упорядочивание:
    // #1 из #2: void(T) из void(U1&): P=T, A=U1 (после корректировки), ок
    // #2 из #1: void(T&) из void(U1): P=T (после корректировки), A=U1: ок
    // ни один не является более специализированным относительно T, вызов неоднозначен
}
template<class T>
struct A { A(); };
template<class T>
void h(const T&); // #1
template<class T>
void h(A<T>&);    // #2
void m()
{
    A<int> z;
    h(z); // дедукция из #1: void h(const T &) [T = A<int>]
          // дедукция из #2: void h(A<T> &) [T = int]
    // частичное упорядочивание:
    // #1 из #2: void(const T&) из void(A<U1>&): P=T A=A<U1>: ok T=A<U1>
    // #2 из #1: void(A<T>&) из void(const U1&): P=A<T> A=const U1: не удается
    // #2 более специализирована чем #1 относительно T
    const A<int> z2;
    h(z2); // дедукция из #1: void h(const T&) [T = A<int>]
           // дедукция из #2: void h(A<T>&) [T = int], но подстановка не удается
    // только одна перегрузка для выбора, частичное упорядочивание не применяется, вызывается #1
}

Поскольку контекст вызова учитывает только параметры, для которых есть явные аргументы вызова, пачки параметров функций, (since C++11) параметры-эллипсисы и параметры со значениями по умолчанию, для которых нет явного аргумента вызова, игнорируются:

template<class T>
void f(T);         // #1
template<class T>
void f(T*, int = 1); // #2
void m(int* ip)
{
    int* ip;
    f(ip); // вызывает #2 (T* более специализирован, чем T)
}
template<class T>
void g(T);       // #1
template<class T>
void g(T*, ...); // #2
void m(int* ip)
{
    g(ip); // вызывает #2 (T* более специализирован, чем T)
}
template<class T, class U>
struct A {};
template<class T, class U>
void f(U, A<U, T>* p = 0); // #1
template<class U>
void f(U, A<U, U>* p = 0); // #2
void h()
{
    f<int>(42, (A<int, int>*)0); // вызывает #2
    f<int>(42);                  // ошибка: неоднозначность
}
template<class T>
void g(T, T = T()); // #1
template<class T, class... U>
void g(T, U...);    // #2
void h()
{
    g(42); // ошибка: неоднозначный вызов
}
template<class T, class... U>
void f(T, U...); // #1
template<class T>
void f(T);       // #2
void h(int i)
{
    f(&i); // вызывает #2 из-за правила разрешения неоднозначности между параметр-пакетом и отсутствием параметра
           // (примечание: было неоднозначно между DR692 и DR1395)
}
template<class T, class... U>
void g(T*, U...); // #1
template<class T>
void g(T);        // #2
void h(int i)
{
    g(&i); // OK: вызывается #1 (T* более специализирован, чем T)
}
template<class... T>
int f(T*...);    // #1
template<class T>
int f(const T&); // #2
f((int*)0); // OK: выбирается #2; невариативный шаблон более специализирован, чем
            // вариативный шаблон (было неоднозначно до DR1395, так как дедукция
            // завершалась неудачей в обоих направлениях)
template<class... Args>
void f(Args... args);        // #1
template<class T1, class... Args>
void f(T1 a1, Args... args); // #2
template<class T1, class T2>
void f(T1 a1, T2 a2);        // #3
f();        // вызывает #1
f(1, 2, 3); // вызывает #2
f(1, 2);    // вызывает #3; невариативный шаблон #3 является более
            // специализированным, чем вариативные шаблоны #1 и #2

В процессе частичного упорядочивания при выводе аргументов шаблона параметры шаблона не требуют сопоставления с аргументами, если аргумент не используется ни в одном из типов, рассматриваемых для частичного упорядочивания

template<class T>
T f(int); // #1
template<class T, class U>
T f(U);   // #2
void g()
{
    f<int>(1); // специализация #1 явная: T f(int) [T = int]
               // специализация #2 выводится: T f(U) [T = int, U = int]
    // частичное упорядочивание (рассматривается только тип аргумента):
    // #1 из #2: T(int) из U1(U2): неудача
    // #2 из #1: T(U) из U1(int): ok: U=int, T не используется
    // вызывается #1
}

Частичное упорядочивание шаблонов функций, содержащих пакеты параметров шаблона, не зависит от количества выводимых аргументов для этих пакетов параметров шаблона.

template<class...>
struct Tuple {};
template<class... Types>
void g(Tuple<Types...>);      // #1
template<class T1, class... Types>
void g(Tuple<T1, Types...>);  // #2
template<class T1, class... Types>
void g(Tuple<T1, Types&...>); // #3
g(Tuple<>());            // calls #1
g(Tuple<int, float>());  // calls #2
g(Tuple<int, float&>()); // calls #3
g(Tuple<int>());         // calls #3
(начиная с C++11)

Чтобы скомпилировать вызов шаблона функции, компилятор должен выбрать между нешаблонными перегрузками, шаблонными перегрузками и специализациями шаблонных перегрузок.

template<class T>
void f(T);      // #1: шаблонная перегрузка
template<class T>
void f(T*);     // #2: шаблонная перегрузка
void f(double); // #3: нешаблонная перегрузка
template<>
void f(int);    // #4: специализация #1
f('a');        // вызывает #1
f(new int(1)); // вызывает #2
f(1.0);        // вызывает #3
f(1);          // вызывает #4

Перегрузки функций vs специализации функций

Обратите внимание, что в разрешении перегрузки участвуют только нетемплейтные и основные темплейтные перегрузки. Специализации не являются перегрузками и не учитываются. Только после того, как разрешение перегрузки выберет наиболее подходящий основной шаблон функции, его специализации проверяются на предмет лучшего соответствия.

template<class T>
void f(T);    // #1: перегрузка для всех типов
template<>
void f(int*); // #2: специализация #1 для указателей на int
template<class T>
void f(T*);   // #3: перегрузка для всех типов указателей
f(new int(1)); // вызывает #3, хотя специализация #1 была бы идеальным соответствием

Важно помнить это правило при упорядочивании заголовочных файлов единицы трансляции. Для дополнительных примеров взаимодействия перегрузок функций и специализаций функций, раскройте ниже:

Примеры

Рассмотрим сначала некоторые сценарии, где поиск, зависимый от аргументов, не применяется. Для этого мы используем вызов ( f ) ( t ) . Как описано в ADL , заключение имени функции в круглые скобки отключает поиск, зависимый от аргументов.

  • Несколько перегруженных версий f ( ) объявлены до точки обращения (POR) в g ( ) .
#include <iostream>
struct A {};
template<class T>
void f(T)  { std::cout << "#1\n"; } // перегрузка #1 до f() POR
template<class T>
void f(T*) { std::cout << "#2\n"; } // перегрузка #2 до f() POR
template<class T>
void g(T* t) 
{
    (f)(t); // f() POR
}
int main()
{
    A* p = nullptr;
    g(p); // POR для g() и f()
}
// Обе перегрузки #1 и #2 добавлены в список кандидатов;
// #2 выбрана, так как она лучше соответствует.

Вывод:

#2


  • Более подходящая перегрузка шаблона объявлена после POR.
#include <iostream>
struct A {};
template<class T>
void f(T)  { std::cout << "#1\n"; } // #1
template<class T>
void g(T* t) 
{
    (f)(t); // f() POR
}
template<class T>
void f(T*) { std::cout << "#2\n"; } // #2
int main()
{
    A* p = nullptr;
    g(p); // POR of g() and f()
}
// Only #1 is added to the candidate list; #2 is defined after POR;
// therefore, it is not considered for overloading even if it is a better match.

Вывод:

#1


  • Более подходящая явная специализация шаблона объявлена после POR.
#include <iostream>
struct A {};
template<class T>
void f(T)    { std::cout << "#1\n"; } // #1
template<class T>
void g(T* t) 
{
    (f)(t); // f() POR
}
template<>
void f<>(A*) { std::cout << "#3\n"; } // #3
int main()
{
    A* p = nullptr;
    g(p); // POR of g() and f()
}
// #1 is added to the candidate list; #3 is a better match defined after POR. The
// candidate list consists of #1 which is eventually selected. After that, the explicit 
// specialization #3 of #1 declared after POI is selected because it is a better match. 
// This behavior is governed by 14.7.3/6 [temp.expl.spec] and has nothing to do with ADL.

Вывод:

#3


  • Более подходящая перегрузка шаблона объявлена после POR. Наиболее подходящая явная специализация шаблона объявлена после более подходящей перегрузки.
#include <iostream>
struct A {};
template<class T>
void f(T)    { std::cout << "#1\n"; } // #1
template<class T>
void g(T* t) 
{
    (f)(t); // f() POR
}
template<class T>
void f(T*)   { std::cout << "#2\n"; } // #2
template<>
void f<>(A*) { std::cout << "#3\n"; } // #3
int main()
{
    A* p = nullptr;
    g(p); // POR of g() and f()
}
// #1 является единственным членом списка кандидатов и в конечном итоге выбирается.
// После этого явная специализация #3 пропускается, поскольку она фактически
// специализирует #2, объявленную после POR.

Вывод:

#1


Теперь рассмотрим те случаи, когда используется поиск, зависимый от аргументов (т.е. мы используем более распространенный формат вызова f ( t ) ).

  • Более подходящая перегрузка шаблона объявлена после POR.
#include <iostream>
struct A {};
template<class T>
void f(T)  { std::cout << "#1\n"; } // #1
template<class T>
void g(T* t) 
{
    f(t); // f() POR
}
template<class T>
void f(T*) { std::cout << "#2\n"; } // #2
int main()
{
    A* p = nullptr;
    g(p); // POR of g() and f()
}
// #1 is added to the candidate list as a result of the ordinary lookup;
// #2 is defined after POR but it is added to the candidate list via ADL lookup.
// #2 is selected being the better match.

Вывод:

#2


  • Более подходящая перегрузка шаблона объявлена после POR. Наилучшая подходящая явная специализация шаблона объявлена перед более подходящей перегрузкой.
#include <iostream>
struct A {};
template<class T>
void f(T)    { std::cout << "#1\n"; } // #1
template<class T>
void g(T* t) 
{
    f(t); // f() POR
}
template<>
void f<>(A*) { std::cout << "#3\n"; } // #3
template<class T>
void f(T*)   { std::cout << "#2\n"; } // #2
int main()
{
    A* p = nullptr;
    g(p); // POR of g() and f()
}
// #1 is added to the candidate list as a result of the ordinary lookup;
// #2 is defined after POR but it is added to the candidate list via ADL lookup.
// #2 is selected among the primary templates, being the better match.
// Since #3 is declared before #2, it is an explicit specialization of #1.
// Hence the final selection is #2.

Вывод:

#2


  • Более подходящая перегрузка шаблона объявлена после POR. Наиболее подходящая явная специализация шаблона объявлена последней.
#include <iostream>
struct A {};
template<class T>
void f(T)    { std::cout << "#1\n"; } // #1
template<class T>
void g(T* t) 
{
    f(t); // f() POR
}
template<class T>
void f(T*)   { std::cout << "#2\n"; } // #2
template<>
void f<>(A*) { std::cout << "#3\n"; } // #3
int main()
{
    A* p = nullptr;
    g(p); // POR of g() and f()
}
// #1 is added to the candidate list as a result of the ordinary lookup;
// #2 is defined after POR but it is added to the candidate list via ADL lookup.
// #2 is selected among the primary templates, being the better match.
// Since #3 is declared after #2, it is an explicit specialization of #2;
// therefore, selected as the function to call.

Вывод:

#3


Всякий раз, когда аргументы являются базовыми типами C++, не существует пространств имён, связанных с ADL. Следовательно, эти сценарии идентичны приведённым выше примерам без ADL.

Подробные правила разрешения перегрузки смотрите в разделе overload resolution .

Специализация шаблона функции

Ключевые слова

template , extern (начиная с C++11)

Отчеты о дефектах

Следующие отчеты об изменениях поведения, влияющие на дефекты, были применены задним числом к ранее опубликованным стандартам C++.

DR Применяется к Поведение в опубликованной спецификации Корректное поведение
CWG 214 C++98 точная процедура частичного упорядочивания не была указана спецификация добавлена
CWG 532 C++98 порядок между шаблоном функции-члена нестатического класса
и шаблоном нечленной функции не был указан
спецификация добавлена
CWG 581 C++98 список шаблонных аргументов в явной специализации или
инстанциации шаблона конструктора был разрешен
запрещено
CWG 1321 C++98 было неясно, являются ли одинаковые зависимые имена в
первом объявлении и переобъявлении эквивалентными
они эквивалентны и
значение такое же, как
в первом объявлении
CWG 1395 C++11 вывод завершался неудачей, когда A был из пакета,
и не было правила разрешения для пустого пакета
вывод разрешен,
правило разрешения добавлено
CWG 1406 C++11 тип нового первого параметра, добавленного для
шаблона функции-члена нестатического класса, был
не связан с ref-qualifier этого шаблона
тип является rvalue
ссылочным типом, если
ref-qualifier равен &&
CWG 1446 C++11 тип нового первого параметра, добавленного для шаблона функции-члена
нестатического класса без ref-qualifier, был lvalue ссылочным
типом, даже если этот шаблон функции-члена сравнивается с
шаблоном функции, первый параметр которого имеет rvalue ссылочный тип
тип является
rvalue ссылочным
типом в этом случае
CWG 2373 C++98 новые первые параметры добавлялись к спискам параметров
шаблонов статических функций-членов при частичном упорядочивании
не добавляются

Смотрите также