jawna specjalizacja funkcji składowej klasy szablonu

88

Muszę wyspecjalizować funkcję elementu członkowskiego szablonu dla pewnego typu (powiedzmy podwójnie ). Działa dobrze, podczas gdy Xsama klasa nie jest klasą szablonu, ale kiedy ją robię, szablon GCC zaczyna generować błędy w czasie kompilacji.

#include <iostream>
#include <cmath>

template <class C> class X
{
public:
   template <class T> void get_as();
};

template <class C>
void X<C>::get_as<double>()
{

}

int main()
{
   X<int> x;
   x.get_as();
}

tutaj jest komunikat o błędzie

source.cpp:11:27: error: template-id
  'get_as<double>' in declaration of primary template
source.cpp:11:6: error: prototype for
  'void X<C>::get_as()' does not match any in class 'X<C>'
source.cpp:7:35: error: candidate is:
  template<class C> template<class T> void X::get_as()

Jak mogę to naprawić i jaki jest problem?

Z góry dziękuję.

ledokol
źródło
2
w obecnym standardzie jest to nielegalne, aby się specjalizować, musisz też specjalizować się w klasie ...
Nim
ale działa, jeśli klasa nie jest szablonem. Czy to też jest nielegalne?
ledokol
nie, to jest w porządku, ta zasada dotyczy tylko szablonów klas (AFAIK).
Nim

Odpowiedzi:

108

To nie działa w ten sposób. Trzeba by powiedzieć, co następuje, ale nie jest to poprawne

template <class C> template<>
void X<C>::get_as<double>()
{

}

Jawnie wyspecjalizowane elementy członkowskie wymagają, aby otaczające je szablony klas również były jawnie wyspecjalizowane. Musisz więc powiedzieć co następuje, co pozwoli tylko na specjalizację członka X<int>.

template <> template<>
void X<int>::get_as<double>()
{

}

Jeśli chcesz, aby otaczający szablon nie był wyspecjalizowany, masz kilka możliwości. Wolę przeciążenia

template <class C> class X
{
   template<typename T> struct type { };

public:
   template <class T> void get_as() {
     get_as(type<T>());
   }

private:
   template<typename T> void get_as(type<T>) {

   }

   void get_as(type<double>) {

   }
};
Johannes Schaub - litb
źródło
dlaczego potrzebujesz type<>opakowania? czy rzut 0 na wskaźnik typu Tnie mógłby załatwić sprawy? Chyba nie jest tak elegancki ...
Nim
Wygląda na to, że to naprawdę niemożliwe. Dzięki.
ledokol
3
@Nim dobrze, myślę, że rzutowanie wskaźnika jest brzydkie i nie działałoby dla typów, do których nie można utworzyć wskaźników (referencje). Ponadto posiadanie parametru funkcji jako wskaźnika do typu tablicy bez rozmiaru jest niedozwolone w C ++. Posiadanie go w opakowaniu typu sprawia, że ​​działa on dla wszystkich typów.
Johannes Schaub - litb
2
@ JohannesSchaub-litb: Jakie są inne możliwości przy przeciążeniach? Czy możesz pokazać niektóre z nich?
Jean-Bernard Jansen
2
@ fast-reflexes jego komentarz dotyczył użycia template<typename T> void get_as(T*); void get_as(double*);i przekazania (T*)0.
Johannes Schaub - litb
24

Jeśli ktoś jest w stanie użyć, std::enable_ifmożemy polegać na SFINAE (niepowodzenie zamiany nie jest błędem)

tak by działało (patrz NA ŻYWO ):

#include <iostream>
#include <type_traits>

template <typename C> class X
{
public:
    template <typename T, 
              std::enable_if_t<!std::is_same_v<double,T>, int> = 0> 
    void get_as() { std::cout << "get as T" << std::endl; }

    template <typename T, 
              std::enable_if_t<std::is_same_v<double,T>, int> = 0> 
    void get_as() { std::cout << "get as double" << std::endl; }
};

int main() {
   X<int> d;
   d.get_as<double>();

   return 0;
}

Brzydkie jest to, że przy wszystkich tych enable_if tylko jedna specjalizacja musi być dostępna dla kompilatora, w przeciwnym razie wystąpi błąd ujednoznacznienia. Dlatego domyślne zachowanie „pobierz jako T” wymaga również włączenia, jeśli.

Gabriel
źródło
Zaktualizowałem post, aby był bardziej nowoczesny.
Gabriel