Могу ли я написать реляционные операторы в терминах арифметических операций?

Так что у меня довольно сложная функция:

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 Мне нужно сделать:

  1. arg1 > arg2
  2. bar > 0
  3. bar > -10

В случае minus Мне нужно сделать:

  1. arg1 < arg2
  2. bar < 0
  3. bar < 10

Обратите внимание, что 10 не имеет одинаковый знак в обоих 3s. В настоящее время я решаю все это, передавая второй параметр шаблона (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

и это должно работать.

4

Другие решения

Кроме того @ Ответ Якка Есть несколько способов сделать это. Вот 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 + прыжки на батуте

батуте это метод метапрограммирования, в котором вы используете функцию «батут» в качестве посредника между вызывающей стороной и фактической функцией, которую вы хотите отправить. Здесь мы будем использовать его для сопоставления с соответствующим типом сравнения (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);
}

Live Demo 5

1

По вопросам рекламы [email protected]