C ++ specjalizacja częściowa szablon funkcji?

87

Wiem, że poniższy kod to częściowa specjalizacja klasy:

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


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

Wiem też, że C ++ nie pozwala na częściową specjalizację szablonów funkcji (dozwolone jest tylko pełne). Ale czy mój kod oznacza, że ​​częściowo wyspecjalizowałem mój szablon funkcji dla jednego / tego samego typu argumentów? Ponieważ działa z Microsoft Visual Studio 2010 Express! Jeśli nie, czy mógłbyś wyjaśnić koncepcję częściowej specjalizacji?

#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;
}
Narek
źródło
Poszukaj analogii specjalizacji klasowej. Jeśli nazywa się to specjalizacją klas, to dlaczego miałbym rozważyć to samo dla funkcji, co przeciążanie?
Narek
1
Nie, składnia specjalizacji jest inna. Spójrz na (rzekomą) składnię specjalizacji funkcji w mojej odpowiedzi poniżej.
iammilind
2
Dlaczego nie powoduje to błędu „Call to max is ambigious”? Jak max(5,5)postanawia, max(T const&, T const&) [with T=int]a nie max(T1 const&, T2 const&) [with T1=int and T2=int]?
NHDaly

Odpowiedzi:

81

Częściowa specjalizacja funkcji nie jest jeszcze dozwolona zgodnie ze standardem. W tym przykładzie faktycznie przeciążasz i nie specjalizujesz się w max<T1,T2>funkcji.
Jego składnia powinna wyglądać trochę jak poniżej, gdyby była dozwolona:

// 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;
}

W przypadku szablonów funkcji standard C ++ dopuszcza tylko pełną specjalizację - z wyłączeniem rozszerzeń kompilatora!

iammilind
źródło
1
@Narek, Częściowa specjalizacja funkcji nie jest częścią standardu (z jakichkolwiek powodów). Myślę, że MSVC obsługuje to jako rozszerzenie. Może być po jakimś czasie, na to pozwolą również inne kompilatory.
iammilind
1
@iammilind: Nie ma problemu. Wydaje się, że już to wie. Dlatego próbuje tego również dla szablonu funkcji. Więc zredagowałem to ponownie, teraz wyjaśniając.
Nawaz
20
Czy ktoś może wyjaśnić, dlaczego częściowa specjalizacja jest niedozwolona?
HelloGoodbye
2
@NHDaly, nie daje błędu niejednoznaczności, ponieważ 1 funkcja jest lepiej dopasowana niż druga. Dlaczego wybiera (T, T)się (T1, T2)na (int, int), to dlatego, że byli gwarantuje, że są 2 parametry i oba typy są takie same; ta ostatnia gwarantuje jedynie, że istnieją 2 parametry. Kompilator zawsze wybiera dokładny opis. np. jeśli musisz dokonać wyboru między dwoma opisami „rzeki”, który byś wybrał? „pobór wody” a „pobór wody płynącej”.
iammilind
1
@kfsone, myślę, że ta funkcja jest w trakcie sprawdzania, stąd otwarta do interpretacji. Możesz odnieść się do tej sekcji open-std , którą widziałem w Dlaczego standard C ++ nie zezwala na częściową specjalizację szablonów funkcji?
iammilind
44

Ponieważ częściowa specjalizacja jest niedozwolona - jak wskazywały inne odpowiedzi - możesz ją obejść, używając std::is_samei std::enable_if, jak poniżej:

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);
}

Wynik:

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

Edycja : w przypadku, gdy musisz być w stanie rozpatrzyć wszystkie pozostałe pozostałe przypadki, możesz dodać definicję, która stwierdza, że ​​już potraktowane przypadki nie powinny pasować - w przeciwnym razie wpadniesz w niejednoznaczne definicje. Definicja może brzmieć:

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");
}

Który produkuje:

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

Chociaż to wszystko wygląda trochę nudno, ponieważ musisz powiedzieć kompilatorowi wszystko, co już zrobiłeś, całkiem wykonalne jest potraktowanie do 5 lub kilku dodatkowych specjalizacji.

Rubens
źródło
Naprawdę nie ma takiej potrzeby, ponieważ można to obsłużyć przez przeciążenie funkcji w znacznie prostszy i wyraźniejszy sposób.
Adrian
2
@Adrian Naprawdę nie mogę wymyślić żadnego innego podejścia do przeciążania funkcji, aby rozwiązać ten problem. Zauważyłeś, że częściowe przeciążenie jest niedozwolone, prawda? Podziel się z nami swoim rozwiązaniem, jeśli uważasz, że jest bardziej zrozumiałe.
Rubens,
1
czy jest jakiś inny sposób na łatwe złapanie wszystkich funkcji szablonu?
Nick
15

Co to jest specjalizacja?

Jeśli naprawdę chcesz zrozumieć szablony, powinieneś przyjrzeć się językom funkcjonalnym. Świat szablonów w C ++ jest swoim własnym językiem czysto funkcjonalnym.

W językach funkcjonalnych selekcji dokonuje się za pomocą dopasowania wzorców :

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

Jak widać, przeciążamy definicję isJust.

Cóż, szablony klas C ++ działają dokładnie w ten sam sposób. Podajesz główną deklarację, która określa liczbę i charakter parametrów. Może to być tylko deklaracja, lub też działać jako definicja (Twój wybór), a następnie możesz (jeśli chcesz) podać specjalizacje wzorca i skojarzyć z nimi inną (inaczej byłoby to głupio) wersję klasy .

W przypadku funkcji szablonów specjalizacja jest nieco bardziej niezręczna: nieco koliduje z rozwiązywaniem przeciążenia. W związku z tym zdecydowano, że specjalizacja będzie dotyczyć wersji niespecjalistycznej, a specjalizacje nie będą brane pod uwagę podczas rozwiązywania problemu z przeciążeniem. Dlatego algorytm wyboru odpowiedniej funkcji wygląda następująco:

  1. Rozpoznawanie przeciążenia między zwykłymi funkcjami i niespecjalistycznymi szablonami
  2. Jeśli wybierzesz niespecjalizowany szablon, sprawdź, czy istnieje dla niego specjalizacja, która byłaby lepiej dopasowana

(informacje na temat dogłębnego leczenia, patrz GotW # 49 )

Jako taka, szablonowa specjalizacja funkcji jest obywatelem drugiej strefy (dosłownie). Jeśli o mnie chodzi, byłoby nam lepiej bez nich: nie spotkałem się jeszcze z przypadkiem, w którym użycie specjalizacji szablonowej nie mogłoby zostać rozwiązane poprzez przeciążenie.

Czy to specjalizacja dotycząca szablonów?

Nie, to po prostu przeciążenie i to jest w porządku. W rzeczywistości przeciążenia zwykle działają tak, jak się tego spodziewamy, podczas gdy specjalizacje mogą być zaskakujące (pamiętajcie o artykule w GotW, do którego dołączyłem).

Matthieu M.
źródło
"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."A co z parametrami innymi niż szablonowe?
Jules GM
@Julius: nadal możesz używać przeciążania, aczkolwiek wprowadzając parametr fikcyjny, taki jak boost::mpl::integral_c<unsigned, 3u>. Innym rozwiązaniem mogłoby być użycie enable_if/ disable_if, chociaż to już inna historia.
Matthieu M.
7

Nieklasowa, niezmienna częściowa specjalizacja jest niedozwolona, ​​ale jak powiedziano:

Wszystkie problemy w informatyce można rozwiązać na innym poziomie pośrednictwa. —— David Wheeler

Dodanie klasy w celu przekazania wywołania funkcji może rozwiązać ten problem, oto przykład:

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
źródło
4

Nie. Na przykład możesz prawnie specjalizować się std::swap, ale nie możesz prawnie zdefiniować własnego przeciążenia. Oznacza to, że nie możesz std::swappracować dla własnego niestandardowego szablonu klasy.

Przeciążenie i częściowa specjalizacja mogą w niektórych przypadkach mieć ten sam efekt, ale nie we wszystkich.

Szczeniak
źródło
4
Dlatego umieszczasz swapprzeciążenie w przestrzeni nazw.
jpalecek
2

Spóźniona odpowiedź, ale niektórym spóźnionym czytelnikom może się to przydać: Czasami funkcja pomocnicza - zaprojektowana w taki sposób, że może być wyspecjalizowana - może również rozwiązać problem.

Wyobraźmy sobie więc, co próbowaliśmy rozwiązać:

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);
}

OK, częściowa specjalizacja funkcji szablonu, nie możemy tego zrobić ... Więc „wyeksportuj” część potrzebną do specjalizacji do funkcji pomocniczej, wyspecjalizujmy ją i użyjmy:

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

Może to być interesujące, zwłaszcza jeśli alternatywy (normalne przeciążenia zamiast specjalizacji, obejście zaproponowane przez Rubensa, ... - nie to, że są złe lub moje jest lepsze, po prostu inna ) miałyby wspólny kod.

Aconcagua
źródło