Так что у меня довольно сложная функция:
template <typename T>
void foo(const int param1, const int param2, int& out_param)
Дано int bar
, const int arg1
, а также const int arg2
функция будет вызываться либо: foo<plus<int>>(arg1, arg2, bar)
или же foo<minus<int>>(arg1, arg2, bar)
Внутренне функция довольно сложна, но я делаю различные операторы реляции, основанные на типе функтора, который был передан как параметр шаблона.
В случае plus
Мне нужно сделать:
arg1 > arg2
bar > 0
bar > -10
В случае minus
Мне нужно сделать:
arg1 < arg2
bar < 0
bar < 10
Обратите внимание, что 10
не имеет одинаковый знак в обоих 3s. В настоящее время я решаю все это, передавая второй параметр шаблона (less
или же greater
.) Но я подумал, что может иметь смысл записать эти отношения как арифметические операции. Это вообще возможно, или мне нужно взять второй параметр шаблона?
T{}(0, arg1) > T{}(0,arg2);
T{}(0, bar) > 0;
T{}(0, bar) > -10;
Основная идея a > b
если и только если -a < -b
, А также plus(0,a)==a
в то время как minus(0,a)==-a
,
Последний хитрый, так как мы хотим изменить порядок <
и знак. К счастью, они отменяют:
Предположим, мы хотим постоянную -10
в плюсе и 10
в минусовом случае. затем
plus(0,-10)
является -10
а также
minus(0,-10)
является 10
,
Итак, мы получаем:
T{}(0, bar) > T{}(0, T{}(0,-10))
в плюсе, rhs 0+0+-10
ака -10
,
В минусовом случае это 0-(0-(-10))
ака -10
,
Итак, краткая форма:
T{}(0,bar) > -10
и это должно работать.
Кроме того @ Ответ Якка Есть несколько способов сделать это. Вот 5.
Это более классический метод, который использовался до того, как стали доступны более продвинутые методы метапрограммирования шаблонов. Это все еще довольно удобно. Мы специализируемся на некоторых структурах в зависимости от 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));
}
Еще одна «классическая» техника, которая остается полезной. Вы, вероятно, знакомы с этой техникой, но я показываю ее для полноты. Пока тебе не нужно частично специализируя функцию, вы можете написать другую версию для нужных вам типов:
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;
}
Третий классический метод, который превращает проверку типа в проблему перегрузки. Суть в том, что мы определяем легкий вес 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>{});
}
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;
}
}
constexpr
+ прыжки на батутебатуте это метод метапрограммирования, в котором вы используете функцию «батут» в качестве посредника между вызывающей стороной и фактической функцией, которую вы хотите отправить. Здесь мы будем использовать его для сопоставления с соответствующим типом сравнения (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);
}