перегрузка шаблона функции - PullRequest
20 голосов
/ 01 февраля 2010

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

Например, задан шаблон функции

template<typename X, typename Y> void func(X x, Y y) {}

что за шаблон перегруженной функции?

1) template<typename X> void func(X x, int y) {}
2) template<typename X, typename Y> X func(X x, Y y) {}
3) template<class X, class Y, class Z> void func(X x, Y y, Z z) {}

Ответы [ 4 ]

27 голосов
/ 01 февраля 2010

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

Вы можете использовать два других:

template<typename X> void func(X x, int y);

будет использоваться, если вторым аргументом вызова является int, например, func("string", 10);

template<class X, class Y, class Z> void func(X x, Y y, Z z);

будет использоваться, если вы вызовете func с тремя аргументами.


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

14.5.5

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

Не шаблонная (или "менее шаблонная") перегрузка предпочтительнее шаблонов, например

template <class T> void foo(T);
void foo(int);

foo(10); //calls void foo(int)
foo(10u); //calls void foo(T) with T = unsigned

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

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

template <class T> void foo(T);
template <class T> void foo(T*);

int i;
int* p;
int arr[10];

foo(i);  //calls first
foo(p);   //calls second
foo(arr); //calls second: array decays to pointer

Более формальное описание всех правил вы можете найти в той же главе стандарта ( Шаблоны функций )


И, наконец, есть ситуации, когда две или более перегрузок будут неоднозначными:

template <class T> void foo(T, int);
template <class T> void foo(int, T);

foo(1, 2);

Здесь призыв неоднозначен, поскольку оба кандидата одинаково специализированы.

Вы можете устранить неоднозначность в таких ситуациях с помощью (например) boost::disable_if. Например, мы можем указать, что когда T = int, вторая перегрузка не должна включаться в качестве кандидата на перегрузку:

#include <boost/utility/enable_if.hpp>
#include <boost/type_traits/is_same.hpp>
template <class T>
void foo(T x, int i);

template <class T>
typename boost::disable_if<boost::is_same<int, T> >::type
foo(int i, T x);

foo(1, 2); //calls the first

Здесь библиотека выдает «сбой замещения» в типе возврата второй перегрузки, если T = int, удаляя его из набора кандидатов на перегрузку.

На практике вам редко приходится сталкиваться с подобными ситуациями.

3 голосов
/ 01 февраля 2010

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

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

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

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

Возможно, вам удастся решить вашу конкретную проблему с помощью перегрузок шаблонов функций, но у вас могут возникнуть проблемы с исправлением любой ошибки, возникающей из-за того, что правила длиннее и меньше людей знакомы с их сложностями. После нескольких лет взлома шаблонов я не знал, что даже незначительная перегрузка шаблонов функций возможна. В таких библиотеках, как Boost и GCC STL, вездесущий альтернативный подход. Используйте шаблонный класс-оболочку:

template< typename X, typename Y >
struct functor {
    void operator()( X x, Y y );
};
template< typename X > // partial specialization: 
struct functor< X, int > { // alternative to overloading for classes
    void operator()( X x, int y );
};

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

template< typename X, typename Y > void func( X x, Y y ) {
    return functor< X, Y >()( x, y );
}

Мне было бы интересно узнать, может ли перегрузка функций делать что-либо (кроме дедукции), что специализация класса [частичная] не может ...

И тогда, конечно, ваша перегрузка # 3 никогда не столкнется с неоднозначностью, поскольку она имеет иное количество аргументов, чем любая другая перегрузка.

1 голос
/ 01 февраля 2010

В дополнение к комментариям, дополнительная информация по теме в статье Херба Саттерса Почему бы не специализировать шаблоны функций Надеюсь, это тоже будет полезно.

1 голос
/ 01 февраля 2010

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


Считайте, что шаблонирование похоже на препроцессор макроса, который раскрывает #defines до того, как компилятор их увидит.

Компилятор "развернет" параметры вашего шаблона и затем рассмотрит объявления вашей функции. Итак, параметр шаблона == параметр функции. Если вы объявите одну и ту же функцию дважды, вы получите ошибку.

Вы спрашиваете о типе возврата. Это часть подписи функции. Две функции с одинаковыми параметрами, но разными типами возвращаемых значений - это две разные функции.

...