Часткова спеціалізація шаблону функції C ++?


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 ++ допускає лише повну спеціалізацію , - виключаючи розширення компілятора!


1
@Narek, Часткова спеціалізація функцій не є частиною стандарту (з якихось причин). Я думаю, що MSVC підтримує це як розширення. Можливо, через деякий час це дозволять інші компілятори.
iammilind

1
@iammilind: Немає проблем. Здається, він це вже знає. Ось чому він намагається це зробити і для шаблону функції. Тож я відредагував його ще раз, давши зрозуміти це зараз.
Nawaz

20
Той, хто може пояснити, чому часткова спеціалізація заборонена?
HelloGoodbye

2
@NHDaly, це не дає помилки двозначності, оскільки 1 функція краще відповідає іншій. Чому він вибирає (T, T)більш (T1, T2)для (int, int), тому , що колишні гарантії , що є 2 параметра і обидва типи однакові; остання лише гарантує наявність 2 параметрів. Компілятор завжди вибирає точний опис. Наприклад, якщо вам потрібно зробити вибір між 2 описами "річки", який би ви вибрали? "збір води" проти "збір води, що тече".
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, "");

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

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.