Могу ли я написать реляционные операторы в терминах арифметических операций?
Так что у меня довольно сложная функция:
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
не имеет одинаковый знак в течение 3 с. В настоящее время я решаю все это, передав второй параметр шаблона (less
или же greater
.) Но я подумал, что может иметь смысл записать эти отношения как арифметические операции. Это вообще возможно, или мне нужно взять второй параметр шаблона?
2 ответа
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
и это должно работать.
Помимо ответа @Yakk, есть несколько способов сделать это. Вот 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));
}
Live Demo 1
Способ 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;
}
Live Demo 2
Способ 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>{});
}
Live Demo 3
Метод 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;
}
}
Live Demo 4
Способ 5: constexpr
+ прыжки на батуте
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);
}