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

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

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
12 дек. '18 в 22:56
источник поделиться
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
12 дек. '18 в 23: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

Начиная с С++ 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

1
13 дек. '18 в 19:45
источник

Посмотрите другие вопросы по меткам или Задайте вопрос