Частичная специализация шаблона

Надоело перегружать функцию для каждого типа? Хватит это терпеть!

Сегодня мы рассмотрим как создать одну шаблонную функцию, которая будет ‘включенной’ для заданных типов и ‘выключенной’ для остальных.

Предположим, что у нас есть какая-то шаблонная функция, которая должна выполнятся только если параметр функции 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;
}

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *