Частичная специализация шаблона функции с ++?

Я знаю, что приведенный ниже код является частичной специализацией класса:

template <typename T1, typename T2> 
class MyClass { 
  … 
}; 


// partial specialization: both template parameters have same type 
template <typename T> 
class MyClass<T,T> { 
  … 
}; 

Также я знаю, что C ++ не допускает частичную специализацию шаблона функции (допускается только полная). Но означает ли мой код, что я частично специализировал свой шаблон функции для аргументов одного / того же типа? Потому что это работает для Microsoft Visual Studio 2010 Express! Если нет, не могли бы вы объяснить концепцию частичной специализации?

#include <iostream>
using std::cin;
using std::cout;
using std::endl;

template <typename T1, typename T2> 
inline T1 max (T1 const& a, T2 const& b) 
{ 
    return a < b ? b : a; 
} 

template <typename T> 
inline T const& max (T const& a, T const& b)
{
    return 10;
}


int main ()
{
    cout << max(4,4.2) << endl;
    cout << max(5,5) << endl;
    int z;
    cin>>z;
}

person Narek    schedule 09.11.2011    source источник
comment
Найдите аналогию со специализацией класса. Если это называется специализацией классов, то почему я должен рассматривать то же самое для функции как перегрузку ??   -  person Narek    schedule 09.11.2011
comment
Нет, синтаксис специализации другой. Посмотрите на (предполагаемый) синтаксис специализации функций в моем ответе ниже.   -  person iammilind    schedule 09.11.2011
comment
Почему это не вызывает ошибку Call to max? Как max(5,5) решает max(T const&, T const&) [with T=int], а не max(T1 const&, T2 const&) [with T1=int and T2=int]?   -  person NHDaly    schedule 13.08.2015


Ответы (6)


Частичная специализация функции пока не разрешена стандартом. В этом примере вы на самом деле перегружаете и не специализируете функцию max<T1,T2>.
Ее синтаксис должен был выглядеть несколько, как показано ниже, если бы было разрешено:

// Partial specialization is not allowed by the spec, though!
template <typename T> 
inline T const& max<T,T> (T const& a, T const& b)
{                  ^^^^^ <--- [supposed] specializing here
  return 10;
}

В случае шаблонов функций стандартом C ++ разрешена только полная специализация, за исключением расширений компилятора!

person iammilind    schedule 09.11.2011
comment
Это не разрешено, потому что это может запутать компилятор, запутав правильную специализированную версию функции, не так ли? Если это правда, то что будет сбивать с толку компилятор, если функция не была перегружена так, как я написал, а была специализирована так, как вы написали (недопустимым способом)? - person Narek; 09.11.2011
comment
@Narek, Частичная специализация функций не входит в стандарт (по каким-то причинам). Думаю, MSVC поддерживает его как расширение. Может быть, когда-нибудь это будет разрешено и другими компиляторами. - person iammilind; 09.11.2011
comment
@iammilind: Отредактировал пост и +1. - person Nawaz; 09.11.2011
comment
@Nawaz, без проблем, в последней строке я действительно хотел указать как in the case of *classes* only **specialization** is allowed (поскольку перегрузка предназначена только для функций). Однако я пропустил classes и испортил предложение. - person iammilind; 09.11.2011
comment
@iammilind: Нет проблем. Кажется, он уже это знает. Вот почему он пробует это и для шаблона функции. Так что я отредактировал его снова, чтобы теперь было ясно. - person Nawaz; 09.11.2011
comment
Кто может объяснить почему частичная специализация не разрешена? - person HelloGoodbye; 19.09.2015
comment
Почему это не вызывает ошибку Call to max? Как max(5,5) решает max(T const&, T const&) [with T=int], а не max(T1 const&, T2 const&) [with T1=int and T2=int]? - person NHDaly; 22.10.2015
comment
@NHDaly, это не дает ошибки двусмысленности, потому что одна функция лучше, чем другая. Почему он выбирает (T, T) вместо (T1, T2) для (int, int), потому что первый гарантирует, что есть 2 параметра и оба типа одинаковы; последний только гарантирует, что есть 2 параметра. Компилятор всегда выбирает точное описание. например Если вам нужно сделать выбор между двумя описаниями реки, какое из них вы бы выбрали? сбор воды против сбора текущей воды. - person iammilind; 23.10.2015
comment
Стандарт явно запрещает частичную спецификацию или просто не определяет ее? - person kfsone; 21.12.2018
comment
@kfsone, я думаю, что эта функция находится на рассмотрении, поэтому открыта для интерпретации. Вы можете сослаться на этот раздел open-std, который я видел в Почему стандарт C ++ не допускает частичную специализацию шаблона функций? - person iammilind; 22.12.2018

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

template <typename T, class F>
inline typename std::enable_if<std::is_same<T, int>::value, void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with ints! " << f << std::endl;
}

template <typename T, class F>
inline typename std::enable_if<std::is_same<T, float>::value, void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with floats! " << f << std::endl;
}

int main(int argc, char *argv[]) {
    typed_foo<int>("works");
    typed_foo<float>(2);
}

Выход:

$ ./a.out 
>>> messing with ints! works
>>> messing with floats! 2

Изменить: если вам нужно иметь возможность обрабатывать все остальные оставшиеся случаи, вы можете добавить определение, в котором говорится, что уже обработанные случаи не должны совпадать - в противном случае вы d попадут в двусмысленные определения. Определение могло быть таким:

template <typename T, class F>
inline typename std::enable_if<(not std::is_same<T, int>::value)
    and (not std::is_same<T, float>::value), void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with unknown stuff! " << f << std::endl;
}

int main(int argc, char *argv[]) {
    typed_foo<int>("works");
    typed_foo<float>(2);
    typed_foo<std::string>("either");
}

Что производит:

$ ./a.out 
>>> messing with ints! works
>>> messing with floats! 2
>>> messing with unknown stuff! either

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

person Rubens    schedule 19.01.2014
comment
На самом деле в этом нет необходимости, поскольку с этим можно справиться путем перегрузки функций гораздо проще и понятнее. - person Adrian; 05.01.2015
comment
@Adrian Я действительно не могу придумать какой-либо другой подход к перегрузке функций, чтобы решить эту проблему. Вы заметили, что частичная перегрузка недопустима? Поделитесь с нами своим решением, если считаете его более понятным. - person Rubens; 09.01.2015
comment
есть ли другой способ легко поймать все шаблонные функции? - person Nick; 06.09.2016
comment
@Adrian Верно, что в некоторых случаях можно переопределить typed_foo таким образом, чтобы он принимал только один аргумент шаблона вместо двух, а затем использовать перегрузку, как вы сказали. Однако ОП на самом деле не об этом. И, кроме того, я не уверен, что вы можете выполнять всеобъемлющую функцию только с перегрузками. Кроме того, вы можете захотеть, чтобы ваша всеобъемлющая реализация вызывала ошибку компиляции при ее использовании, что возможно только с функциями шаблона, где зависящая от шаблона строка вызовет компилятор выдал ошибку. - person AnthonyD973; 23.07.2021

Что такое специализация?

Если вы действительно хотите разбираться в шаблонах, вам следует взглянуть на функциональные языки. Мир шаблонов в C ++ - это отдельный чисто функциональный подъязык.

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

-- An instance of Maybe is either nothing (None) or something (Just a)
-- where a is any type
data Maybe a = None | Just a

-- declare function isJust, which takes a Maybe
-- and checks whether it's None or Just
isJust :: Maybe a -> Bool

-- definition: two cases (_ is a wildcard)
isJust None = False
isJust Just _ = True

Как видите, мы перегружаем определение isJust.

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

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

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

(подробные сведения см. в GotW № 49)

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

Это специализация шаблона?

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

person Matthieu M.    schedule 09.11.2011
comment
"As such, template specialization of functions is a second-zone citizen (literally). As far as I am concerned, we would be better off without them: I have yet to encounter a case where a template specialization use could not be solved with overloading instead." Как насчет параметров шаблона, отличного от типа? - person Jules G.M.; 25.07.2013
comment
@Julius: вы все еще можете использовать перегрузку, хотя и введя фиктивный параметр, такой как boost::mpl::integral_c<unsigned, 3u>. Другим решением может быть использование _2 _ / _ 3_, хотя это другая история. - person Matthieu M.; 25.07.2013

Неклассовая, неизменяемая частичная специализация не допускается, но, как сказано:

Все проблемы в информатике могут быть решены другим уровнем косвенного обращения. —— Дэвид Уиллер

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

template <class Tag, class R, class... Ts>
struct enable_fun_partial_spec;

struct fun_tag {};

template <class R, class... Ts>
constexpr R fun(Ts&&... ts) {
  return enable_fun_partial_spec<fun_tag, R, Ts...>::call(
      std::forward<Ts>(ts)...);
}

template <class R, class... Ts>
struct enable_fun_partial_spec<fun_tag, R, Ts...> {
  constexpr static R call(Ts&&... ts) { return {0}; }
};

template <class R, class T>
struct enable_fun_partial_spec<fun_tag, R, T, T> {
  constexpr static R call(T, T) { return {1}; }
};

template <class R>
struct enable_fun_partial_spec<fun_tag, R, int, int> {
  constexpr static R call(int, int) { return {2}; }
};

template <class R>
struct enable_fun_partial_spec<fun_tag, R, int, char> {
  constexpr static R call(int, char) { return {3}; }
};

template <class R, class T2>
struct enable_fun_partial_spec<fun_tag, R, char, T2> {
  constexpr static R call(char, T2) { return {4}; }
};

static_assert(std::is_same_v<decltype(fun<int>(1, 1)), int>, "");
static_assert(fun<int>(1, 1) == 2, "");

static_assert(std::is_same_v<decltype(fun<char>(1, 1)), char>, "");
static_assert(fun<char>(1, 1) == 2, "");

static_assert(std::is_same_v<decltype(fun<long>(1L, 1L)), long>, "");
static_assert(fun<long>(1L, 1L) == 1, "");

static_assert(std::is_same_v<decltype(fun<double>(1L, 1L)), double>, "");
static_assert(fun<double>(1L, 1L) == 1, "");

static_assert(std::is_same_v<decltype(fun<int>(1u, 1)), int>, "");
static_assert(fun<int>(1u, 1) == 0, "");

static_assert(std::is_same_v<decltype(fun<char>(1, 'c')), char>, "");
static_assert(fun<char>(1, 'c') == 3, "");

static_assert(std::is_same_v<decltype(fun<unsigned>('c', 1)), unsigned>, "");
static_assert(fun<unsigned>('c', 1) == 4, "");

static_assert(std::is_same_v<decltype(fun<unsigned>(10.0, 1)), unsigned>, "");
static_assert(fun<unsigned>(10.0, 1) == 0, "");

static_assert(
    std::is_same_v<decltype(fun<double>(1, 2, 3, 'a', "bbb")), double>, "");
static_assert(fun<double>(1, 2, 3, 'a', "bbb") == 0, "");

static_assert(std::is_same_v<decltype(fun<unsigned>()), unsigned>, "");
static_assert(fun<unsigned>() == 0, "");
person user2709407    schedule 12.01.2018

Нет. Например, вы можете юридически специализироваться std::swap, но вы не можете юридически определить свою собственную перегрузку. Это означает, что вы не можете заставить std::swap работать с вашим собственным шаблоном настраиваемого класса.

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

person Puppy    schedule 09.11.2011
comment
Вот почему вы помещаете свою swap перегрузку в свое пространство имен. - person jpalecek; 09.11.2011

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

Итак, давайте представим, что мы пытались решить:

template <typename R, typename X, typename Y>
void function(X x, Y y)
{
    R* r = new R(x);
    f(r, y); // another template function?
}

// for some reason, we NEED the specialization:
template <typename R, typename Y>
void function<R, int, Y>(int x, Y y) 
{
    // unfortunately, Wrapper has no constructor accepting int:
    Wrapper* w = new Wrapper();
    w->setValue(x);
    f(w, y);
}

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

template <typename R, typename T>
R* create(T t)
{
    return new R(t);
}
template <>
Wrapper* create<Wrapper, int>(int n) // fully specialized now -> legal...
{
    Wrapper* w = new Wrapper();
    w->setValue(n);
    return w;
}

template <typename R, typename X, typename Y>
void function(X x, Y y)
{
    R* r = create<R>(x);
    f(r, y); // another template function?
}

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

person Aconcagua    schedule 22.09.2017