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

87

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

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;
}
Нарек
источник
Найдите аналогию со специализацией класса. Если это называется специализацией классов, то почему я должен рассматривать то же самое для функции как перегрузку ??
Нарек
1
Нет, синтаксис специализации другой. Посмотрите на (предполагаемый) синтаксис специализации функций в моем ответе ниже.
iammilind
2
Почему при этом не выводится ошибка «Максимальный вызов неоднозначен»? Как max(5,5)разрешить max(T const&, T const&) [with T=int]и нет max(T1 const&, T2 const&) [with T1=int and T2=int]?
NHDaly

Ответы:

81

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

iammilind
источник
1
@Narek, Частичная специализация функций не входит в стандарт (по каким-то причинам). Думаю, MSVC поддерживает его как расширение. Возможно, через некоторое время это будет разрешено и другими компиляторами.
iammilind
1
@iammilind: Нет проблем. Кажется, он уже это знает. Вот почему он пробует это и для шаблона функции. Так что я отредактировал его снова, чтобы теперь было ясно.
Nawaz
20
Кто-нибудь может объяснить, почему частичная специализация не разрешена?
HelloGoodbye
2
@NHDaly, это не дает ошибки двусмысленности, потому что одна функция лучше, чем другая. Почему он выбирает (T, T)более (T1, T2)для (int, int), потому , что прежние гарантии , что есть 2 параметра и оба типа одинаковы; последний только гарантирует, что есть 2 параметра. Компилятор всегда выбирает точное описание. например, если вам нужно сделать выбор между двумя описаниями «реки», какое из них вы бы выбрали? «сбор воды» vs «сбор текущей воды».
iammilind
1
@kfsone, я думаю, что эта функция находится на рассмотрении, поэтому открыта для интерпретации. Вы можете сослаться на этот раздел open-std , который я видел в разделе Почему стандарт C ++ не допускает частичную специализацию шаблона функций?
iammilind
44

Поскольку частичная специализация не допускается, как указывали другие ответы, вы можете обойти ее, используя 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

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

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 или еще нескольких специализаций.

Рубенс
источник
На самом деле в этом нет необходимости, поскольку с этим можно справиться путем перегрузки функций гораздо более простым и понятным способом.
Адриан
2
@Adrian Я действительно не могу придумать какой-либо другой подход к перегрузке функций, чтобы решить эту проблему. Вы заметили, что частичная перегрузка недопустима? Поделитесь с нами своим решением, если считаете его более понятным.
Рубенс
1
есть ли другой способ легко поймать все шаблонные функции?
Ник
15

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

Если вы действительно хотите разбираться в шаблонах, вам следует взглянуть на функциональные языки. Мир шаблонов в 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 ++ работают точно так же. Вы предоставляете основное объявление, в котором указывается количество и характер параметров. Это может быть просто объявление или также действовать как определение (по вашему выбору), а затем вы можете (если хотите) предоставить специализации шаблона и связать с ними другую (иначе было бы глупо) версию класса. .

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

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

(подробное описание см. в GotW # 49 )

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

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

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

Матье М.
источник
"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."Как насчет параметров шаблона без типа?
Jules GM
@Julius: вы все равно можете использовать перегрузку, хотя и введя фиктивный параметр, например boost::mpl::integral_c<unsigned, 3u>. Другим решением может быть использование enable_if/ disable_if, хотя это другая история.
Matthieu M.
7

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

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

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

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, "");
user2709407
источник
4

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

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

Щенок
источник
4
Вот почему вы помещаете свою swapперегрузку в свое пространство имен.
jpalecek
2

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

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

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?
}

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

Аконкагуа
источник