Oryginalnym mechanizmem metaprogramowania w C++ były znane z języka C makra, lecz pewnego dnia, Erwin Unruh, przez przypadek odkrył mechanizm dziś znany jako SFINAE (Substitution Failure Is Not An Error). W skrócie, jeżeli kompilatorowi nie uda się wyspecjalizować szablonu będzie próbował dalej. W jego przykładzie wywołując rekursywną specjalizację szablonów, dokonał obliczeń w czasie kompilacji, choć kompilacja zakończyła się niepowodzeniem. Być może, właśnie przez to, że mechanizm ten pojawił się przypadkiem, a nie został wprowadzony z premedytacją, nie jest on przemyślany składniowo, sprawia że wiele osób odrzuca i dzieli społeczność C++ na dwa obozy.
Przykłady poniżej oparłem na prezentacji Nicolai Josuttis - C++17 - The Best Features. Na początek wersja kodu wykorzystująca enable_if przez zwracany typ (drugi parametr - w naszym przypadku std::string).
#include <iostream> #include <type_traits> using namespace std; template<typename T> std::enable_if_t<std::is_arithmetic_v<T>, std::string> as_string1(T x) { return std::to_string(x) + " [is_arithmetic_v]"; } template<typename T> std::enable_if_t<std::is_same_v<T, std::string>, std::string> as_string1(T x) { return x + " [is_same_v]"; } template<typename T> std::enable_if_t<!std::is_same_v<T, std::string> && !std::is_arithmetic_v<T>, std::string> as_string1(T x) { return std::string(x) + " [!is_same_v && !is_arithmetic_v]"; } int main() { cout << as_string1(11) << endl; cout << as_string1(std::string("12")) << endl; cout << as_string1("13") << endl; }Wynik:
11 [is_arithmetic_v] 12 [is_same_v] 13 [!is_same_v && !is_arithmetic_v]Inna wersja, gdzie enable_if pojawia się jako parametr szablonowy. Domyślne argumenty szablonowe, nie są częścią sygnatury funkcji szablonowej, trzeba więc do enable_if przekazać jeszcze dummy typ (int), nie wiem czemu trzeba przypisać do tego 0. Dziwny hack.
#include <iostream> #include <type_traits> using namespace std; template<typename T, std::enable_if_t<std::is_arithmetic_v<T>, int> = 0> std::string as_string2(T x) { return std::to_string(x) + " [is_arithmetic_v]"; } template<typename T, std::enable_if_t<std::is_same_v<T, std::string>, int> = 0> std::string as_string2(T x) { return x + " [is_same_v]"; } template<typename T, std::enable_if_t<!std::is_same_v<T, std::string> && !std::is_arithmetic_v<T>, int> = 0> std::string as_string2(T x) { return std::string(x) + " [!is_same_v && !is_arithmetic_v]"; } int main() { cout << as_string2(21) << endl; cout << as_string2(std::string("22")) << endl; cout << as_string2("23") << endl; }Wynik:
21 [is_arithmetic_v] 22 [is_same_v] 23 [!is_same_v && !is_arithmetic_v]Są jeszcze inne formy zapisu enable_if np. jako parametr funkcji, ale w tej chwili nie jest to istotne. W C++17 pojawił się ciekawy mechanizm if constexpr, który pozwala radzić sobie z dużą liczbą takich specjalizacji, a kod wygląda znacznie bardziej czytelnie.
#include <iostream> #include <type_traits> using namespace std; template<typename T> std::string as_string3(T x) { if constexpr(std::is_arithmetic_v<T>) { return std::to_string(x) + " [is_arithmetic_v]"; } else if constexpr(std::is_same_v<T, std::string>) { return x + " [is_same_v]"; } else { return std::string(x) + " [!is_same_v && !is_arithmetic_v]"; } } int main() { cout << as_string3(31) << endl; cout << as_string3(std::string("32")) << endl; cout << as_string3("33") << endl; }Wynik:
31 [is_arithmetic_v] 32 [is_same_v] 33 [!is_same_v && !is_arithmetic_v]
Brak komentarzy:
Prześlij komentarz