Надоело перегружать функцию для каждого типа? Хватит это терпеть!
Сегодня мы рассмотрим как создать одну шаблонную функцию, которая будет ‘включенной’ для заданных типов и ‘выключенной’ для остальных.
Предположим, что у нас есть какая-то шаблонная функция, которая должна выполнятся только если параметр функции float или double. Самое простое решение — проверять тип в процессе выполнения программы:
#include <typeinfo> // typeid() template <typename Type> void isReal(Type var) { // Проверяем тип в runtime if (typeid(var) != typeid(float) && typeid(var) != typeid(double)) { return; // Завершаем выполнение функции, если не соответствует тип } // Код... }
Но проверка типа во время выполнения программы — это лишние операции — поэтому лучше перенести эту ‘проверку’ во время компиляции.
Начиная с C+11 стандарта у нас появилась возможность ‘включить’ функцию с помощью std::enable_if тремя способами:
#include <type_traits> // std::enable_if, std::is_same using std::enable_if; using std:is_same; /** * 1. Включение функции при помощи возвращаемого типа */ template <class Type> typename enable_if<is_same<int, Type>::value, void>::type foo1(Type var) { // Код... } /** * 2. Включение функции при помощи дополнительного неиспользуемого параметра */ template <class Type> void foo2(Type var, typename enable_if<is_same<int, Type>::value>::type* = 0) { // Код... } /** * 3. Включение функции при помощи дополнительного параметра шаблона */ template <class Type, class = typename enable_if<is_same<int, Type>::value>::type> void foo3(Type var) { // Сигнатура функции не меняется // Код... } int main() { foo1((int) 1); // OK foo1((double) 1); // Ошибка компиляции: не найдена подходящая функция // Тоже самое с другими функциями }
В примере выше используется SFINAE (Substitution failure is not an error) — ошибка при подстановке параметра шаблона (substitution failure) не будет ошибкой компиляции (an error), а всего лишь удалением данной потенциальной перегрузки из возможных кандидатов.
Используя это, можно создавать разные функции для разных типов, например функция check() возвращает true для double и float, а для остальных — false:
#include <type_traits> // std::enable_if, std::is_same #include <iostream> using std::enable_if; using std::is_same; /** * Структура реализует проверку типа шаблона * Если это double или float, то функция check() возвращает true * иначе false */ template <class Type> struct isReal { template<class Q = T> // Включаем функцию, если тип double или float typename enable_if<is_same<Q, double>::value || is_same<Q, float>::value, bool>::type check() { return true; } template<class Q = T> // Включаем функцию, если тип не double и не float typename enable_if<!is_same<Q, double>::value && !is_same<Q, float>::value, bool>::type check() { return false; } }; int main() { isReal<double> d; std::cout << d.check() << std::endl; // Выведет 1 isReal<int> i; std::cout << i.check() << std::endl; // Выведет 0 return 0; }
А в C+17 стандарте с появлением constexpr появилась возможность сделать это еще проще:
#include <type_traits> // std::enable_if, std::is_same #include <iostream> using std::is_same; template<class T> struct isReal { template<class Q = T> bool check() { // Проверка на то, что тип double или float if constexpr(is_same<double, Q>::value || is_same<float, Q>::value) { return true; }else { return false; } } }; int main() { isReal<double> d; isReal<int> i; std::cout << d.check() << std::endl; // Выведет 1 std::cout << i.check() << std::endl; // Выведет 0 return 0; }