我可以根据算术运算写关系运算符吗?

时间:2018-12-12 19:56:30

标签: c++ templates math functor relational-operators

所以我有一个相当复杂的功能:

template <typename T>
void foo(const int param1, const int param2, int& out_param)

给出int barconst int arg1const 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 中的符号不​​同。我目前正在通过传递第二个模板参数(lessgreater)来解决所有这些问题。但是我当时认为将这些关系写为算术运算可能更有意义。甚至有可能吗?还是我需要使用第二个模板参数?

2 个答案:

答案 0 :(得分:4)

T{}(0, arg1) > T{}(0,arg2);
T{}(0, bar) > 0;
T{}(0, bar) > -10;

当且仅当a > b时,基本思想是-a < -b。还有plus(0,a)==aminus(0,a)==-a

最后一个比较棘手,因为我们想更改<的顺序和符号。幸运的是,他们取消了:

假设我们希望一个常量,在正数情况下为-10,在负数情况下为10。然后

plus(0,-10)

-10

minus(0,-10)

10

所以我们得到:

T{}(0, bar) > T{}(0, T{}(0,-10))

在加号的情况下,rh是0+0+-10,又名-10

在减号情况下,这是0-(0-(-10)),又名-10

所以简写为:

T{}(0,bar) > -10

它应该可以工作。

答案 1 :(得分:1)

除了@Yakk's answer以外,还有多种方法可以执行此操作。这是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::greaterstd::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

相关问题