Могу ли я написать реляционные операторы в терминах арифметических операций? - PullRequest
0 голосов
/ 12 декабря 2018

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

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 ]

0 голосов
/ 13 декабря 2018

Помимо @ ответа Якка есть несколько способов сделать это.Вот 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

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

0 голосов
/ 12 декабря 2018
T{}(0, arg1) > T{}(0,arg2);
T{}(0, bar) > 0;
T{}(0, bar) > -10;

Основная идея - a > b тогда и только тогда, когда -a < -bplus(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

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

...