我可以用算术运算的方式写关系运算符吗



所以我有一个相当复杂的函数:

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在两个3s中都没有相同的符号。我目前正在通过传递第二个模板参数(lessgreater(来解决这一切。但我认为将这些关系写成算术运算可能更有意义。这可能吗,或者我需要使用第二个模板参数吗?

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

其基本思想是a > b当且仅当-a < -b。而CCD_ 19和CCD_。

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

假设我们想要一个常数,在加号的情况下是-10,在减号的情况下为10。然后

plus(0,-10)

-10

minus(0,-10)

为CCD_ 25。

所以我们得到:

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));
}

现场演示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;
}

现场演示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>{});
}

现场演示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;
}
}

现场演示4


方法5:constexpr+蹦床

蹦床是一种元编程技术,您可以使用"蹦床"函数作为调用方和您希望发送到的实际函数之间的中介。在这里,我们将使用它来映射到适当的比较类型(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);
}

现场演示5

最新更新