44

Przypuśćmy, że chcesz napisać rodzajowe funkcji void f<T>(), który nie jedno czy T jest rodzajem POD a co innego jeśli T jest non-POD (lub jakiegokolwiek innego arbitralnego orzecznik).Tag wysyłka kontra metody statyczne na częściowo wyspecjalizowanych klas

Jednym ze sposobów osiągnięcia tego celu byłoby wykorzystanie wzoru tag wysyłki jak biblioteki standardowej robi z kategorii iterator:

template <bool> struct podness {}; 
typedef podness<true> pod_tag; 
typedef podness<false> non_pod_tag; 

template <typename T> void f2(T, pod_tag) { /* POD */ } 
template <typename T> void f2(T, non_pod_tag) { /* non-POD */ } 

template <typename T> 
void f(T x) 
{ 
    // Dispatch to f2 based on tag. 
    f2(x, podness<std::is_pod<T>::value>()); 
} 

Alternatywą byłoby użyć statycznej funkcji składowej częściowo wyspecjalizowanych typów:

template <typename T, bool> struct f2; 

template <typename T> 
struct f2<T, true> { static void f(T) { /* POD */ } }; 

template <typename T> 
struct f2<T, false> { static void f(T) { /* non-POD */ } }; 

template <typename T> 
void f(T x) 
{ 
    // Select the correct partially specialised type. 
    f2<T, std::is_pod<T>::value>::f(x); 
} 

Jakie są plusy i minusy zastosowania jednej metody względem drugiej? Które zaleciłbyś?

+3

Cokolwiek unosi łódź. Uważam, że druga wersja jest bardziej "typetryczna" i atrakcyjna, ponieważ jest mniej pomocniczego kodu i mniej ukrytych pojęć. Dodałbym również argumenty za przekazaniem argumentów! –

Odpowiedz

15

Chciałbym tag wysyłkę ponieważ:

  • Łatwy do rozszerzenia z nowymi znacznikami
  • Łatwe w użyciu dziedziczenie (example)
  • To jest dość powszechną techniką programowania ogólnego:

Wydaje mi się, że trudno jest dodać trzeci wariant w drugim przykładzie. Kiedy będziesz chciał dodać, na przykład typ inny niż POD-of-POD, musisz zastąpić bool w template <typename T, bool> struct f2; czymś innym (int, jeśli chcesz =)) i zastąpić wszystkie struct f2<T, bool-value> przez struct f2<T, another-type-value>. Tak więc dla mnie drugi wariant wygląda na trudny do rozszerzenia. Proszę mnie poprawić, jeśli się mylę.

+0

Jednym ze sposobów jego rozszerzenia byłoby użycie parametru szablonu enum, ale zgadzam się, że byłoby to trochę bardziej niezgrabne. –

15

czytelny alternatywą [boost|std]::enable_if, tagi i częściowa specjalizacja dla prosty kompilacji wysyłki, które lubię jest następujące:

[Pamiętaj, że wartości logiczne mają konwersję do liczb całkowitych, że tablice zerowej długości są nieprawidłowe i że obraźliwe szablony są odrzucane (SFINAE). Również char (*)[n] jest wskaźnikiem do tablicy n elementów.]

template <typename T> 
void foo(T, char (*)[is_pod<T>::value] = 0) 
{ 
    // POD 
} 

template <typename T> 
void foo(T, char (*)[!is_pod<T>::value] = 0) 
{ 
    // Non POD 
} 

ma również tę zaletę, że nie potrzebuje klas zewnętrznych, które zanieczyszczają przestrzeń nazw. Teraz, jeśli chcemy uzewnętrznić predykat jak w swoim pytaniu, można zrobić:

template <bool what, typename T> 
void foo(T, char (*)[what] = 0) 
{ 
    // taken when what is true 
} 

template <bool what, typename T> 
void foo(T, char (*)[!what] = 0) 
{ 
    // taken when what is false 
} 

Zastosowanie:

foo<std::is_pod<T>::value>(some_variable); 
+3

+1 Bardzo sprytny. – templatetypedef

+0

Tak, to jest sprytne, ale jeśli nie wiesz o SFINAE, ma to poważny wpływ na "WTF", którego inni nie mają. Doceniam jednak zwięzłość. –

+1

@Peter: SFINAE ma już duży współczynnik WTF. Używanie "nazwa-pliku std :: enable_if :: type" naprawdę zaciemnia element 'what' part imho. Tutaj jest czysto, w jego nawiasach. –

9

W rzeczywistości oba są tylko wzorcem wysyłki znacznika. Poprzedni nazywa się tagiem wysyłającym przez instancję, a drugi to wysyłający tag według typu.

Barend, główny autor Boost.Geometry, explains zarówno metody i preferuje to drugie. Jest to szeroko stosowane w Boost.Geometry.Here're zalety podsumować:

  • Jest zbędne wystąpienia znacznika, ponieważ jego jedynym celem jest, aby różnicować
  • łatwo jest zdefiniować nowe typy i stałe oparte na znacznikach
  • Argumenty mogą być odwrócone w interfejs tj powiedzieć distance(point, polygon); i distance(polygon, point); może zarówno mieć tylko jedną implementację
1

wiem, że to jest stary pytanie z odpowiedzią już przyjęte, ale to może być realną alternatywą:

template<typename T> 
std::enable_if_t<std::is_pod<T>::value> f(T pod) 
{ 
} 

template<typename T> 
std::enable_if_t<!std::is_pod<T>::value> f(T non_pod) 
{ 
}