Помимо @ ответа Якка есть несколько способов сделать это.Вот 5.
Метод 1: Черты функций
Это более классический метод, использовавшийся до того, как стали доступны более сложные методы метапрограммирования шаблонов.Это все еще довольно удобно.Мы специализируем некоторую структуру в зависимости от T
, чтобы дать нам типы и константы, которые мы хотим использовать.
template<class T>
struct FooTraits;
template<class T>
struct FooTraits<std::plus<T>>
{
using Compare = std::greater<T>;
static constexpr std::tuple<int, int> barVals{0, 10};
};
template<class T>
struct FooTraits<std::minus<T>>
{
using Compare = std::less<T>;
static constexpr std::tuple<int, int> barVals{0, -10};
};
template <class T>
void foo(const int arg1, const int arg2, int& bar)
{
using traits = FooTraits<T>;
typename traits::Compare cmp{};
cmp(arg1, arg2);
cmp(bar, std::get<0>(traits::barVals));
cmp(bar, std::get<1>(traits::barVals));
}
Метод 2: Полная специализация
Еще одна «классическая» техника, которая остается полезной.Вы, вероятно, знакомы с этой техникой, но я показываю ее для полноты.Пока вам не нужно частично специализировать функцию, вы можете написать другую версию для нужных вам типов:
template <class T>
void foo(const int arg1, const int arg2, int& bar);
template <>
void foo<std::plus<int>>(const int arg1, const int arg2, int& bar)
{
arg1 > arg2;
bar > 0;
bar > 10;
}
template <>
void foo<std::minus<int>>(const int arg1, const int arg2, int& bar)
{
arg1 < arg2;
bar < 0;
bar < -10;
}
Метод 3. Отправка с тегами
Третий классический метод, который превращает проверку типа в проблему перегрузки.Суть в том, что мы определяем некоторую легковесную tag
структуру, которую мы можем создать, а затем используем ее как различие между перегрузками.Часто это удобно использовать, когда у вас есть шаблонная функция класса, и вы не хотите специализировать весь класс только для того, чтобы специализировать указанную функцию.
namespace detail
{
template<class...> struct tag{};
void foo(const int arg1, const int arg2, int& bar, tag<std::plus<int>>)
{
arg1 > arg2;
bar > 0;
bar > 10;
}
void foo(const int arg1, const int arg2, int& bar, tag<std::minus<int>>)
{
arg1 < arg2;
bar < 0;
bar < -10;
}
}
template <class T>
void foo(const int arg1, const int arg2, int& bar)
{
return detail::foo(arg1, arg2, bar, detail::tag<T>{});
}
Метод 4: Простой constexpr if
Начиная с C ++ 17 мы можем использовать if constexpr
блоки для проверки типа во время компиляции.Это полезно, потому что если проверка не проходит , компилятор вообще не компилирует этот блок .Это часто приводит к гораздо более простому коду, чем раньше, когда нам приходилось использовать сложное косвенное обращение к классам или функциям с расширенным метапрограммированием:
template <class T>
void foo(const int arg1, const int arg2, int& bar)
{
if constexpr (std::is_same_v<T, std::plus<int>>)
{
arg1 > arg2;
bar > 0;
bar > 10;
}
if constexpr(std::is_same_v<T, std::minus<int>>)
{
arg1 < arg2;
bar < 0;
bar < -10;
}
}
Метод 5: constexpr
+ trampolining
trampolining - это метод метапрограммирования, в котором вы используете функцию «батут» в качестве посредника между вызывающим абонентом и фактической функцией, которую вы хотите отправить на,Здесь мы будем использовать его для сопоставления с соответствующим типом сравнения (std::greater
или std::less
), а также с интегральными константами, с которыми мы хотим сравнить bar
.Он немного более гибкий, чем метод 4. Он также немного разделяет проблемы.За счет читабельности:
namespace detail
{
template<class Cmp, int first, int second>
void foo(const int arg1, const int arg2, int& bar)
{
Cmp cmp{};
cmp(arg1, arg2);
cmp(bar, first);
cmp(bar, second);
}
}
template <class T>
void foo(const int arg1, const int arg2, int& bar)
{
if constexpr (std::is_same_v<T, std::plus<int>>)
return detail::foo<std::greater<int>, 0, 10>(arg1, arg2, bar);
if constexpr(std::is_same_v<T, std::minus<int>>)
return detail::foo<std::less<int>, 0, -10>(arg1, arg2, bar);
}