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

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

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 не имеет одинаковый знак в течение 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);
}

Live Demo 5

Другие вопросы по тегам