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