Надоело перегружать функцию для каждого типа? Хватит это терпеть!
Сегодня мы рассмотрим как создать одну шаблонную функцию, которая будет ‘включенной’ для заданных типов и ‘выключенной’ для остальных.
Предположим, что у нас есть какая-то шаблонная функция, которая должна выполнятся только если параметр функции 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;
}