Scott Meyers opublikował treść i status swojej następnej książki EC ++ 11. Napisał, że jedną z pozycji w książce może być „Unikaj std::enable_if
w sygnaturach funkcji” .
std::enable_if
może być używany jako argument funkcji, jako typ zwracany lub jako szablon klasy lub parametr szablonu funkcji do warunkowego usuwania funkcji lub klas z rozpoznawania przeciążenia.
W tym pytaniu pokazane są wszystkie trzy rozwiązania.
Jako parametr funkcji:
template<typename T>
struct Check1
{
template<typename U = T>
U read(typename std::enable_if<
std::is_same<U, int>::value >::type* = 0) { return 42; }
template<typename U = T>
U read(typename std::enable_if<
std::is_same<U, double>::value >::type* = 0) { return 3.14; }
};
Jako parametr szablonu:
template<typename T>
struct Check2
{
template<typename U = T, typename std::enable_if<
std::is_same<U, int>::value, int>::type = 0>
U read() { return 42; }
template<typename U = T, typename std::enable_if<
std::is_same<U, double>::value, int>::type = 0>
U read() { return 3.14; }
};
Jako typ zwrotny:
template<typename T>
struct Check3
{
template<typename U = T>
typename std::enable_if<std::is_same<U, int>::value, U>::type read() {
return 42;
}
template<typename U = T>
typename std::enable_if<std::is_same<U, double>::value, U>::type read() {
return 3.14;
}
};
- Które rozwiązanie powinno być preferowane i dlaczego powinienem unikać innych?
- W jakich przypadkach „Unikaj
std::enable_if
w sygnaturach funkcji” dotyczy użycia jako typu zwracanego (co nie jest częścią normalnej sygnatury funkcji, ale specjalizacji szablonów)? - Czy są jakieś różnice w szablonach funkcji składowych i niebędących członkami?
std::enable_if
zaśmiecać moich sygnatur funkcji (zwłaszcza brzydkiejnullptr
wersji argumentu funkcji dodatkowej ), ponieważ zawsze wygląda na to, co to jest, dziwny hack (cośstatic if
może zrobić dużo piękniejsze i bardziej przejrzyste), używając czarnej magii szablonów, aby wykorzystać ciekawą funkcję języka. Dlatego wolę wysyłanie tagów, gdy tylko jest to możliwe (cóż, nadal masz dodatkowe dziwne argumenty, ale nie w interfejsie publicznym, a także znacznie mniej brzydkie i tajemnicze ).=0
w tym stanietypename std::enable_if<std::is_same<U, int>::value, int>::type = 0
? Nie mogłem znaleźć odpowiednich zasobów, aby to zrozumieć. Wiem, że pierwsza część=0
ma typ członka,int
jeśliU
iint
jest taki sam. Wielkie dzięki!Odpowiedzi:
Umieść hack w parametrach szablonu .
enable_if
Od parametru szablonu podejście ma co najmniej dwie zalety nad innymi:czytelność : użycie enable_if i typy return / argument nie są łączone w jeden niechlujny fragment ujednoznaczników nazw typów i zagnieżdżonych typów dostępu; mimo że bałagan związany z ujednoznaczniaczem i typem zagnieżdżonym można złagodzić za pomocą szablonów aliasów, nadal łączyłoby to ze sobą dwie niepowiązane rzeczy. Użycie enable_if jest związane z parametrami szablonu, a nie z typami zwracanymi. Posiadanie ich w parametrach szablonu oznacza, że są bliżej tego, co ma znaczenie;
uniwersalność zastosowania : konstruktory nie mają typów zwracanych, a niektóre operatory nie mogą mieć dodatkowych argumentów, więc żadna z pozostałych dwóch opcji nie może być stosowana wszędzie. Umieszczenie parametru enable_if w parametrze szablonu działa wszędzie, ponieważ i tak możesz używać SFINAE tylko w szablonach.
Dla mnie aspekt czytelności jest dużym czynnikiem motywującym do tego wyboru.
źródło
FUNCTION_REQUIRES
makro tutaj , sprawia, że znacznie ładniejszy czytać, i to działa w C ++ 03 kompilatory, jak również i to polega na użyciuenable_if
w rodzaju powrotnego. Ponadto użycieenable_if
parametrów szablonu funkcji powoduje problemy z przeciążeniem, ponieważ teraz sygnatura funkcji nie jest unikalna, co powoduje niejednoznaczne błędy związane z przeciążeniem.enable_if
z domyślnym parametrem szablonu innego niż typ, który umożliwia przeciążenie. To znaczyenable_if_t<condition, int> = 0
zamiasttypename = enable_if_t<condition>
.flamingdangerzone
odsyłacz w Twoim komentarzu wydaje się teraz prowadzić do strony instalacji oprogramowania szpiegującego. Oznaczyłem to do uwagi moderatora.std::enable_if
opiera się na zasadzie „ Awaria podmiany nie jest błędem ” (aka SFINAE) podczas dedukcji argumentów szablonu . Jest to bardzo delikatna funkcja językowa i musisz bardzo uważać, aby zrobić ją dobrze.enable_if
zawiera zagnieżdżony szablon lub definicję typu (wskazówka: szukaj::
tokenów), wtedy rozdzielczość tych zagnieżdżonych szablonów lub typów jest zwykle niewyedukowanym kontekstem . Każde niepowodzenie podstawienia w takim niewyedukowanym kontekście jest błędem .enable_if
przeciążeniach nie mogą się nakładać, ponieważ rozdzielczość przeciążenia byłaby niejednoznaczna. Jest to coś, co ty jako autor musisz sprawdzić samodzielnie, chociaż otrzymujesz dobre ostrzeżenia kompilatora.enable_if
manipuluje zestawem wykonalnych funkcji podczas rozwiązywania przeciążenia, które mogą mieć zaskakujące interakcje w zależności od obecności innych funkcji, które są sprowadzane z innych zakresów (np. przez ADL). To sprawia, że nie jest zbyt wytrzymały.Krótko mówiąc, kiedy to działa, działa, ale jeśli nie, debugowanie może być bardzo trudne. Bardzo dobrą alternatywą jest użycie wysyłania tagów , tj. Delegowanie do funkcji implementacji (zwykle w
detail
przestrzeni nazw lub w klasie pomocniczej), która odbiera fałszywy argument oparty na tym samym warunku kompilacji, którego używasz wenable_if
.Wysyłanie tagów nie manipuluje zestawem przeciążenia, ale pomaga wybrać dokładnie żądaną funkcję, dostarczając odpowiednie argumenty za pomocą wyrażenia w czasie kompilacji (np. W charakterystyce typu). Z mojego doświadczenia wynika, że jest to znacznie łatwiejsze do debugowania i poprawienia. Jeśli jesteś początkującym pisarzem bibliotecznym o wyrafinowanych cechach typu, możesz tego potrzebować
enable_if
, ale w przypadku większości regularnych zastosowań warunków kompilacji nie jest to zalecane.źródło
enable_if
można to zrobić dobrze?is_f_able
Swoją drogą , pisanie takich cech jest zadaniem, które uważam za zadanie dla pisarzy bibliotek, którzy oczywiście mogą używać SFINAE, jeśli daje im to przewagę, ale dla „zwykłych” użytkowników i posiadających cechęis_f_able
, myślę, że wysyłanie tagów jest łatwiejsze.Parametr szablonu
Może być łatwo użyty nieprawidłowo i powoduje błędy przy przeciążeniach:
Zauważ
typename = std::enable_if_t<cond>
zamiast poprawiaćstd::enable_if_t<cond, int>::type = 0
typ zwrotu:
Na koniec parametr funkcji:
+
,-
,*
, ...)void* = nullptr
) (więc wskaźnik funkcji będzie się różnić i tak dalej)Istnieją subtelne różnice w dziedziczeniu i
using
:Według
using-declarator
(wyróżnienie moje):namespace.udecl
Tak więc zarówno dla argumentu szablonu, jak i typu zwracanego, metody są ukryte, wygląda to następująco:
Demo (gcc błędnie znajduje funkcję podstawową).
Podczas gdy w przypadku argumentacji działa podobny scenariusz:
Próbny
źródło