我可以根据算术运算来编写关系运算符吗?
Can I Write Relational Operators in Terms of Arithmetic Operations?
所以我有一个相当复杂的功能:
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
的情况下我需要做的是:
arg1 > arg2
bar > 0
bar > -10
在minus
的情况下我需要做的是:
arg1 < arg2
bar < 0
bar < 10
请注意 10
在两个 3 中没有相同的符号。我目前通过传递第二个模板参数(less
或 greater
)来解决所有这些问题,但我认为将这些关系写为算术运算可能更有意义。这甚至可能吗,还是我需要采用第二个模板参数?
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
它应该可以工作。
除了 之外,您还可以通过多种方式执行此操作。这里有 5.
方法一:函数特征
这更像是一种 classic 技术,在更高级的模板元编程技术可用之前使用。它仍然非常方便。我们根据 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
方法二:全专业化
另一种 "classic" 技术仍然有用。您可能熟悉这种技术,但我展示它是为了完整起见。只要您不需要部分 专门化一个函数,您就可以为您需要的类型编写不同的版本:
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
方法三:标记派发
第三种 classic 技术将类型检查转变为重载问题。要点是我们定义了一些我们可以实例化的轻量级 tag
结构,然后将其用作重载之间的区分器。当你有一个模板化的 class 函数,并且你不想专门化整个 class 只是为了专门化所述函数时,这通常很适合使用。
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
方法四:直截了当constexpr if
从 C++17 开始,我们可以使用 if constexpr
块对类型进行编译时检查。这些很有用,因为如果检查失败编译器根本不会编译该块。这通常会导致比以前更简单的代码,我们不得不使用复杂的间接访问 classes 或具有高级元编程的函数:
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
方法五:constexpr
+蹦床
trampoling 是一种元编程技术,您可以在其中使用 "trampoline" 函数作为调用者和您希望分派到的实际函数之间的中介。在这里,我们将使用它来映射到适当的比较类型(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
所以我有一个相当复杂的功能:
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
的情况下我需要做的是:
arg1 > arg2
bar > 0
bar > -10
在minus
的情况下我需要做的是:
arg1 < arg2
bar < 0
bar < 10
请注意 10
在两个 3 中没有相同的符号。我目前通过传递第二个模板参数(less
或 greater
)来解决所有这些问题,但我认为将这些关系写为算术运算可能更有意义。这甚至可能吗,还是我需要采用第二个模板参数?
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
它应该可以工作。
除了
方法一:函数特征
这更像是一种 classic 技术,在更高级的模板元编程技术可用之前使用。它仍然非常方便。我们根据 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
方法二:全专业化
另一种 "classic" 技术仍然有用。您可能熟悉这种技术,但我展示它是为了完整起见。只要您不需要部分 专门化一个函数,您就可以为您需要的类型编写不同的版本:
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
方法三:标记派发
第三种 classic 技术将类型检查转变为重载问题。要点是我们定义了一些我们可以实例化的轻量级 tag
结构,然后将其用作重载之间的区分器。当你有一个模板化的 class 函数,并且你不想专门化整个 class 只是为了专门化所述函数时,这通常很适合使用。
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
方法四:直截了当constexpr if
从 C++17 开始,我们可以使用 if constexpr
块对类型进行编译时检查。这些很有用,因为如果检查失败编译器根本不会编译该块。这通常会导致比以前更简单的代码,我们不得不使用复杂的间接访问 classes 或具有高级元编程的函数:
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
方法五:constexpr
+蹦床
trampoling 是一种元编程技术,您可以在其中使用 "trampoline" 函数作为调用者和您希望分派到的实际函数之间的中介。在这里,我们将使用它来映射到适当的比较类型(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);
}