Пространства имён
Варианты
Действия

Шаблон функции

Материал из cppreference.com
< cpp‎ | language
 
 
Язык С++
Общие темы
Управление потоком
Операторы условного выполнения
if
Операторы итерации (циклы)
Операторы перехода
Функции
Объявление функции
Выражение лямбда-функции
Спецификатор inline
Спецификации динамических исключений (до C++17*)
Спецификатор noexcept (C++11)
Исключения
Пространства имён
Типы
Спецификаторы
decltype (C++11)
auto (C++11)
alignas (C++11)
Спецификаторы длительности хранения
Инициализация
Выражения
Альтернативные представления
Литералы
Логические - Целочисленные - С плавающей запятой
Символьные - Строковые - nullptr (C++11)
Определяемые пользователем (C++11)
Утилиты
Атрибуты (C++11)
Types
Объявление typedef
Объявление псевдонима типа (C++11)
Casts
Неявные преобразования - Явные преобразования
static_cast - dynamic_cast
const_cast - reinterpret_cast
Выделение памяти
Классы
Свойства функции класса
explicit (C++11)
static
Специальные функции-элементы
Шаблоны
Шаблон класса
Шаблон функции
Разное
 
 

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

Содержание

[править] Синтаксис

template < список-параметров > объявление-функции (1)
template < список-параметров > requires ограничение объявление-функции (2) (начиная с C++20)
объявление-функции-с-заполнителями (3) (начиная с C++20)
export template < список-параметров > объявление-функции (4) (убрано в C++11)

[править] Объяснение

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

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

(до C++11)

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

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

void f1(auto); // то же, что и template<class T> void f1(T)
void f2(C1 auto); // то же, что template<C1 T> void f2(T), если C1 является концептом
void f3(C2 auto...); // то же, что template<C2... Ts> void f3(Ts...),
                     // если C2 является концептом
void f4(const C3 auto*, C4 auto&); // то же, что template<C3 T, C4 U>
                                              // void f4(const T*, U&);
 
template<class T, C U>
void g(T x, U y, C auto z); // то же, что и template<class T, C U, C W>
                                         // void g(T x, U y, W z);

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

template<>
void f4<int>(const int*, const double&); // специализация f4<int, const double>


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

[править] Создание экземпляра шаблона функции

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

[править] Явное создание экземпляра

template возвращаемый-тип имя < список-аргументов > ( список-параметров ) ; (1)
template возвращаемый-тип имя ( список-параметров ) ; (2)
extern template возвращаемый-тип имя < список-аргументов > ( список-параметров ) ; (3) (начиная с C++11)
extern template возвращаемый-тип имя ( список-параметров ) ; (4) (начиная с C++11)
1) Явное определение создания экземпляра (без вывода аргумента шаблона, если каждый параметр шаблона, отличный от значения по умолчанию, указан явно)
2) Явное определение создания экземпляра с выводом аргумента шаблона для всех параметров
3) Явное объявление создания экземпляра (без вывода аргумента шаблона, если явно указан каждый параметр шаблона, отличный от значения по умолчанию)
4) Явное объявление создания экземпляра с выводом аргумента шаблона для всех параметров

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

Явное объявление создания экземпляра (внешний шаблон) предотвращает неявное создание экземпляра: код, который в противном случае вызвал бы неявное создание экземпляра, должен использовать определение явного создания экземпляра, предоставленное где-то еще в программе.

(начиная с 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)).

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

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

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

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

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

[править] Неявное создание экземпляра

Когда код ссылается на функцию в контексте, требующем наличия определения функции, или если существование определения влияет на семантику программы (начиная с 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 потенциально не оценивается как константа
    h<false, int>(0); // ошибка: создаёт экземпляр f<int>, даже если B оценивается
                      // как false и инициализация списка значений int из int не может
                      // быть сужена
}
(начиная с 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 << "Привет, мир" << std::endl;
    // operator<< просматривается через ADL как std::operator<<,
    // затем выводится как operator<<<char, std::char_traits<char>> оба раза
    // std::endl выводится как &std::endl<char, std::char_traits<char>>
}

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

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

[править] Явные аргументы шаблона

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

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

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

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

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

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

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

template<class... Types>
void f(Types... values);
 
void g()
{
    f<int*, float*>(0, 0, 0); // Типы = {int*, float*, int}
}
(начиная с C++11)

[править] Подстановка аргумента шаблона

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

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

После подстановки все параметры функции массивы и типа функции преобразуются в указатели, а все 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 является 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()

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

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

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

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

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

(начиная с 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) когда размещающий оператор delete, который является специализацией шаблона функции, выбирается для соответствия размещающему оператору new:
4) когда объявление дружественной функции, явное создание экземпляра или явная специализация ссылаются на функцию специализацию шаблона:
template<class X>
void f(X a);  // первый шаблон f
template<class X>
void f(X* a); // второй шаблон f
template<>
void f<>(int *a) {} // явная специализация
 
// Вывод шаблонного аргумента предлагает двух кандидатов:
// f<int*>(int*) и f<int>(int*)
// частичный порядок выбирает f<int>(int*) как более специализированный

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

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

  • Для каждого параметра типа, нетипа и шаблона , включая пакеты параметров, (начиная с C++11) создаётся уникальный фиктивный тип, значение или шаблон, который подставляется в тип шаблона функции.
  • Если только один из двух сравниваемых шаблонов функций является функцией-элементом, и этот шаблон функции является нестатическим элементом некоторого класса A, новый первый параметр вставляется в его список параметров. Учитывая cv как cv-квалификаторы шаблона функции и 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&) как более специализированный
}

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

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

  • в контексте вызова функции типы это те типы параметров функции, для которых вызов функции имеет аргументы (аргументы функции по умолчанию, пакеты параметров, (начиная с 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. В противном случае ни один из шаблонов не является более специализированным, чем другой.

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

(начиная с 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*: вывод ok: 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*: ok
    // #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*: ok
    // #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: fails
 
    // #2 из #1: void(T,int*) из void(U1,U2*): P1=T A1=U1: T=U1
    //                                         P2=int* A2=U2*: fails
 
    // ни один из них не является более специализированным по отношению к 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 (после согласования), ok
 
    // #2 из #1: void(T&) из void(U1): P=T (после согласования), A=U1: ok
 
    // ни один из них не является более специализированным по отношению к 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
}

Так как в контексте вызова рассматриваются только параметры, для которых есть явные аргументы вызова, те пакеты параметров функции, (начиная с 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<>());            // вызывает #1
g(Tuple<int, float>());  // вызывает #2
g(Tuple<int, float&>()); // вызывает #3
g(Tuple<int>());         // вызывает #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

[править] Перегрузки функций и специализации функций

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

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 - point-of-reference) в 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 g() и f()
}
 
// В список кандидатов добавляется только #1; #2 определяется после POR;
// поэтому она не считается перегруженной, даже если она лучше подходит.

Вывод:

#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 g() и f()
}
 
// #1 добавлена в список кандидатов; #3 лучшее совпадение, определённое после POR.
// Список кандидатов состоит из #1, которая в конечном итоге выбирается. После
// этого выбирается явная специализация #3 из #1, объявленная после POI, поскольку
// она лучше соответствует. Это поведение регулируется 14.7.3/6 [temp.expl.spec]
// и не имеет ничего общего с 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 g() и 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 g() и f()
}
 
// #1 добавляется в список кандидатов в результате обычного поиска;
// #2 определяется после POR, но добавляется в список кандидатов через поиск ADL.
// #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<>
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 g() и f()
}
 
// #1 добавляется в список кандидатов в результате обычного поиска;
// #2 определяется после POR, но добавляется в список кандидатов через поиск ADL.
// #2 выбирается среди основных шаблонов, так как она лучше подходит.
// Поскольку #3 объявлена перед #2, это явная специализация #1.
// Следовательно, окончательный выбор #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 g() и f()
}
 
// #1 добавляется в список кандидатов в результате обычного поиска;
// #2 определяется после POR, но добавляется в список кандидатов через поиск ADL.
// #2 выбирается среди основных шаблонов, так как она лучше подходит.
// Поскольку #3 объявлена после #2, это явная специализация #2;
// поэтому выбрана в качестве вызываемой функции.

Вывод:

#3


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

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

[править] Специализация шаблона функции

[править] Отчёты о дефектах

Следующие изменения поведения были применены с обратной силой к ранее опубликованным стандартам C++:

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

[править] Смотрите также