比较具有替代顺序的自定义类型的 std::tuple(或 std::pair)。是否可以插入自定义小于/比较功能?
Comparing std::tuple (or std::pair) of custom types who has alternative orderings. Is it possible to plug-in a custom less-than / comparison function?
问题
我有一个自定义类型 A
,它具有自然排序(具有 operator<
)和多个替代排序(区分大小写、不区分大小写等)。现在我有一个 std::pair
(或 std::tuple
),其中包含(一个或多个)A
。以下是我要比较的类型的一些示例:std::pair<A, int>
、std::pair<int, A>
、std::tuple<A, int, int>
、std::tuple<int, A, int>
。我如何使用默认的逐元素比较实现来比较 std::pair
(或 std::tuple
),插入我的 A
?
比较函数
代码
以下代码无法编译:
#include <utility> // std::pair
#include <tuple> // std::tuple
#include <iostream> // std::cout, std::endl
struct A
{
A(char v) : value(v) {}
char value;
};
// LOCATION-1 (explained in the text below)
int main()
{
std::cout
<< "Testing std::pair of primitive types: "
<< (std::pair<char, int>('A', 1)
<
std::pair<char, int>('a', 0))
<< std::endl;
std::cout
<< "Testing std::tuple of primitive types: "
<< (std::tuple<char, int, double>('A', 1, 1.0)
<
std::tuple<char, int, double>('a', 0, 0.0))
<< std::endl;
// This doesn't compile:
std::cout
<< "Testing std::pair of custom types: "
<< (std::pair<A, int>('A', 1)
<
std::pair<A, int>('a', 0))
<< std::endl;
return 0;
}
这是因为 operator<
没有为 struct A
定义。加上上面的LOCATION-1
就可以解决问题:
bool operator<(A const& lhs, A const& rhs)
{
return lhs.value < rhs.value;
}
现在,我们有 struct A
的替代顺序:
bool case_insensitive_less_than(A const& lhs, A const& rhs)
{
char const lhs_value_case_insensitive
= ('a' <= lhs.value && lhs.value <= 'z'
? (lhs.value + 0x20)
: lhs.value);
char const rhs_value_case_insensitive
= ('a' <= rhs.value && rhs.value <= 'z'
? (rhs.value + 0x20)
: rhs.value);
return lhs_value_case_insensitive < rhs_value_case_insensitive;
}
假设我们想为 struct A
(区分大小写的)保留原始的 operator<
,我们如何将 std::pair<A, int>
与这种替代顺序进行比较?
我知道为 std::pair<A, int>
添加一个特殊版本的 operator<
可以解决问题:
bool operator<(std::pair<A, int> const& lhs, std::pair<A, int> const& rhs)
{
return (case_insensitive_less_than(lhs.first, rhs.first)
? true
: case_insensitive_less_than(rhs.first, lhs.first)
? false
: (lhs.second < rhs.second));
}
但是,我认为这是一个次优的解决方案。
首先,对于std::pair
,重新实现逐元素比较很容易,但对于std::tuple
,它可能很复杂(处理可变参数模板)并且容易出错。
其次,我简直不敢相信这是解决问题的最佳实践方法:假设我们必须为以下每个 类 定义一个专门版本的 operator<
: std::tuple<A, int, int>
, std::tuple<int, A, int>
, std::tuple<int, int, A>
, std::tuple<A, A, int>
, ...(这甚至不是一个实用的方法!)
为 std::tuple
重新使用编写良好的内置 operator<
并为 struct A
插入我的 less-than
将是我想要的。可能吗?提前致谢!
最简单的方法是手动编写 compare( tup, tup, f )
,它使用 f
按字典顺序比较元组中的元素。但这很无聊。
// This type wraps a reference of type X&&
// it then overrides == and < with L and E respectively
template<class X, class L, class E>
struct reorder_ref {
using ref = reorder_ref;
X&& x;
friend bool operator<(ref lhs, ref rhs) {
return L{}((X&&) lhs.x, (X&&) rhs.x);
}
friend bool operator==(ref lhs, ref rhs) {
return E{}((X&&) lhs.x, (X&&) rhs.x);
}
// other comparison ops based off `==` and `<` go here
friend bool operator!=(ref lhs, ref rhs){return !(lhs==rhs);}
friend bool operator>(ref lhs, ref rhs){return rhs<lhs;}
friend bool operator<=(ref lhs, ref rhs){return !(lhs>rhs);}
friend bool operator>=(ref lhs, ref rhs){return !(lhs<rhs);}
reorder_ref(X&& x_) : x((X&&) x_) {}
reorder_ref(reorder_ref const&) = default;
};
以上是改变我们订购方式的参考。
// a type tag, to pass a type to a function:
template<class X>class tag{using type=X;};
// This type takes a less than and equals stateless functors
// and takes as input a tuple, and builds a tuple of reorder_refs
// basically it uses L and E to compare the elements, but otherwise
// uses std::tuple's lexographic comparison code.
template<class L, class E>
struct reorder_tuple {
// indexes trick:
template<class Tuple, class R, size_t... Is>
R operator()(tag<R>, std::index_sequence<Is...>, Tuple const& in) const {
// use indexes trick to do conversion
return R( std::get<Is>(in)... );
}
// forward to the indexes trick above:
template<class... Ts, class R=std::tuple<reorder_ref<Ts const&, L, E>...>>
R operator()(std::tuple<Ts...> const& in) const {
return (*this)(tag<R>{}, std::index_sequence_for<Ts...>{}, in);
}
// pair filter:
template<class... Ts, class R=std::pair<reorder_ref<Ts const&, L, E>...>>
R operator()(std::pair<Ts...> const& in) const {
return (*this)(tag<R>{}, std::index_sequence_for<Ts...>{}, in);
}
};
上面的无状态函数对象采用一些新的less和equals操作,并将任何元组映射到reorder_ref<const T, ...>
的元组,这改变了顺序分别遵循L
和E
.
下一个类型的作用与 std::less<void>
对 std::less<T>
的作用类似——它采用类型特定的无状态排序函数模板对象,并使其成为类型通用的无状态排序函数对象:
// This takes a type-specific ordering stateless function type, and turns
// it into a generic ordering function type
template<template<class...> class order>
struct generic_order {
template<class T>
bool operator()(T const& lhs, T const& rhs) const {
return order<T>{}(lhs, rhs);
}
};
所以如果我们有一个 template<class T>class Z
使得 Z<T>
是 T
上的一个排序,上面给出了任何东西的通用排序。
下一张是我的最爱。它采用类型 T,并根据到类型 U 的映射对其进行排序。这非常有用:
// Suppose there is a type X for which we have an ordering L
// and we have a map O from Y->X. This builds an ordering on
// (Y lhs, Y rhs) -> L( O(lhs), O(rhs) ). We "order" our type
// "by" the projection of our type into another type. For
// a concrete example, imagine we have an "id" structure with a name
// and age field. We can write a function "return s.age;" to
// map our id type into ints (age). If we order by that map,
// then we order the "id" by age.
template<class O, class L = std::less<>>
struct order_by {
template<class T, class U>
bool operator()(T&& t, U&& u) const {
return L{}( O{}((T&&) t), O{}((U&&) u) );
}
};
现在我们把它们粘合在一起:
// Here is where we build a special order. Suppose we have a template Z<X> that returns
// a stateless order on type X. This takes that ordering, and builds an ordering on
// tuples based on it, using the above code as glue:
template<template<class...>class Less, template<class...>class Equals=std::equal_to>
using tuple_order = order_by< reorder_tuple< generic_order<Less>, generic_order<Equals> > >;
tuple_order
为我们做了大部分工作。我们所需要的只是为它提供一个按元素排序 template
的无状态函数对象。 tuple_order
然后将生成一个基于它的元组排序仿函数。
// Here is a concrete use of the above
// my_less is a sorting functiont that sorts everything else the usual way
// but it sorts Foo's backwards
// Here is a toy type. It wraps an int. By default, it sorts in the usual way
struct Foo {
int value = 0;
// usual sort:
friend bool operator<( Foo lhs, Foo rhs ) {
return lhs.value<rhs.value;
}
friend bool operator==( Foo lhs, Foo rhs ) {
return lhs.value==rhs.value;
}
};
template<class T>
struct my_less : std::less<T> {};
// backwards sort:
template<>
struct my_less<Foo> {
bool operator()(Foo const& lhs, Foo const& rhs) const {
return rhs.value < lhs.value;
}
};
using special_order = tuple_order< my_less >;
and bob is your uncle (live example).
special_order
可以传递给 std::map
或 std::set
,它将对遇到的任何元组或对进行排序 my_less
以替换元素的默认顺序。
问题
我有一个自定义类型 A
,它具有自然排序(具有 operator<
)和多个替代排序(区分大小写、不区分大小写等)。现在我有一个 std::pair
(或 std::tuple
),其中包含(一个或多个)A
。以下是我要比较的类型的一些示例:std::pair<A, int>
、std::pair<int, A>
、std::tuple<A, int, int>
、std::tuple<int, A, int>
。我如何使用默认的逐元素比较实现来比较 std::pair
(或 std::tuple
),插入我的 A
?
代码
以下代码无法编译:
#include <utility> // std::pair
#include <tuple> // std::tuple
#include <iostream> // std::cout, std::endl
struct A
{
A(char v) : value(v) {}
char value;
};
// LOCATION-1 (explained in the text below)
int main()
{
std::cout
<< "Testing std::pair of primitive types: "
<< (std::pair<char, int>('A', 1)
<
std::pair<char, int>('a', 0))
<< std::endl;
std::cout
<< "Testing std::tuple of primitive types: "
<< (std::tuple<char, int, double>('A', 1, 1.0)
<
std::tuple<char, int, double>('a', 0, 0.0))
<< std::endl;
// This doesn't compile:
std::cout
<< "Testing std::pair of custom types: "
<< (std::pair<A, int>('A', 1)
<
std::pair<A, int>('a', 0))
<< std::endl;
return 0;
}
这是因为 operator<
没有为 struct A
定义。加上上面的LOCATION-1
就可以解决问题:
bool operator<(A const& lhs, A const& rhs)
{
return lhs.value < rhs.value;
}
现在,我们有 struct A
的替代顺序:
bool case_insensitive_less_than(A const& lhs, A const& rhs)
{
char const lhs_value_case_insensitive
= ('a' <= lhs.value && lhs.value <= 'z'
? (lhs.value + 0x20)
: lhs.value);
char const rhs_value_case_insensitive
= ('a' <= rhs.value && rhs.value <= 'z'
? (rhs.value + 0x20)
: rhs.value);
return lhs_value_case_insensitive < rhs_value_case_insensitive;
}
假设我们想为 struct A
(区分大小写的)保留原始的 operator<
,我们如何将 std::pair<A, int>
与这种替代顺序进行比较?
我知道为 std::pair<A, int>
添加一个特殊版本的 operator<
可以解决问题:
bool operator<(std::pair<A, int> const& lhs, std::pair<A, int> const& rhs)
{
return (case_insensitive_less_than(lhs.first, rhs.first)
? true
: case_insensitive_less_than(rhs.first, lhs.first)
? false
: (lhs.second < rhs.second));
}
但是,我认为这是一个次优的解决方案。
首先,对于std::pair
,重新实现逐元素比较很容易,但对于std::tuple
,它可能很复杂(处理可变参数模板)并且容易出错。
其次,我简直不敢相信这是解决问题的最佳实践方法:假设我们必须为以下每个 类 定义一个专门版本的 operator<
: std::tuple<A, int, int>
, std::tuple<int, A, int>
, std::tuple<int, int, A>
, std::tuple<A, A, int>
, ...(这甚至不是一个实用的方法!)
为 std::tuple
重新使用编写良好的内置 operator<
并为 struct A
插入我的 less-than
将是我想要的。可能吗?提前致谢!
最简单的方法是手动编写 compare( tup, tup, f )
,它使用 f
按字典顺序比较元组中的元素。但这很无聊。
// This type wraps a reference of type X&&
// it then overrides == and < with L and E respectively
template<class X, class L, class E>
struct reorder_ref {
using ref = reorder_ref;
X&& x;
friend bool operator<(ref lhs, ref rhs) {
return L{}((X&&) lhs.x, (X&&) rhs.x);
}
friend bool operator==(ref lhs, ref rhs) {
return E{}((X&&) lhs.x, (X&&) rhs.x);
}
// other comparison ops based off `==` and `<` go here
friend bool operator!=(ref lhs, ref rhs){return !(lhs==rhs);}
friend bool operator>(ref lhs, ref rhs){return rhs<lhs;}
friend bool operator<=(ref lhs, ref rhs){return !(lhs>rhs);}
friend bool operator>=(ref lhs, ref rhs){return !(lhs<rhs);}
reorder_ref(X&& x_) : x((X&&) x_) {}
reorder_ref(reorder_ref const&) = default;
};
以上是改变我们订购方式的参考。
// a type tag, to pass a type to a function:
template<class X>class tag{using type=X;};
// This type takes a less than and equals stateless functors
// and takes as input a tuple, and builds a tuple of reorder_refs
// basically it uses L and E to compare the elements, but otherwise
// uses std::tuple's lexographic comparison code.
template<class L, class E>
struct reorder_tuple {
// indexes trick:
template<class Tuple, class R, size_t... Is>
R operator()(tag<R>, std::index_sequence<Is...>, Tuple const& in) const {
// use indexes trick to do conversion
return R( std::get<Is>(in)... );
}
// forward to the indexes trick above:
template<class... Ts, class R=std::tuple<reorder_ref<Ts const&, L, E>...>>
R operator()(std::tuple<Ts...> const& in) const {
return (*this)(tag<R>{}, std::index_sequence_for<Ts...>{}, in);
}
// pair filter:
template<class... Ts, class R=std::pair<reorder_ref<Ts const&, L, E>...>>
R operator()(std::pair<Ts...> const& in) const {
return (*this)(tag<R>{}, std::index_sequence_for<Ts...>{}, in);
}
};
上面的无状态函数对象采用一些新的less和equals操作,并将任何元组映射到reorder_ref<const T, ...>
的元组,这改变了顺序分别遵循L
和E
.
下一个类型的作用与 std::less<void>
对 std::less<T>
的作用类似——它采用类型特定的无状态排序函数模板对象,并使其成为类型通用的无状态排序函数对象:
// This takes a type-specific ordering stateless function type, and turns
// it into a generic ordering function type
template<template<class...> class order>
struct generic_order {
template<class T>
bool operator()(T const& lhs, T const& rhs) const {
return order<T>{}(lhs, rhs);
}
};
所以如果我们有一个 template<class T>class Z
使得 Z<T>
是 T
上的一个排序,上面给出了任何东西的通用排序。
下一张是我的最爱。它采用类型 T,并根据到类型 U 的映射对其进行排序。这非常有用:
// Suppose there is a type X for which we have an ordering L
// and we have a map O from Y->X. This builds an ordering on
// (Y lhs, Y rhs) -> L( O(lhs), O(rhs) ). We "order" our type
// "by" the projection of our type into another type. For
// a concrete example, imagine we have an "id" structure with a name
// and age field. We can write a function "return s.age;" to
// map our id type into ints (age). If we order by that map,
// then we order the "id" by age.
template<class O, class L = std::less<>>
struct order_by {
template<class T, class U>
bool operator()(T&& t, U&& u) const {
return L{}( O{}((T&&) t), O{}((U&&) u) );
}
};
现在我们把它们粘合在一起:
// Here is where we build a special order. Suppose we have a template Z<X> that returns
// a stateless order on type X. This takes that ordering, and builds an ordering on
// tuples based on it, using the above code as glue:
template<template<class...>class Less, template<class...>class Equals=std::equal_to>
using tuple_order = order_by< reorder_tuple< generic_order<Less>, generic_order<Equals> > >;
tuple_order
为我们做了大部分工作。我们所需要的只是为它提供一个按元素排序 template
的无状态函数对象。 tuple_order
然后将生成一个基于它的元组排序仿函数。
// Here is a concrete use of the above
// my_less is a sorting functiont that sorts everything else the usual way
// but it sorts Foo's backwards
// Here is a toy type. It wraps an int. By default, it sorts in the usual way
struct Foo {
int value = 0;
// usual sort:
friend bool operator<( Foo lhs, Foo rhs ) {
return lhs.value<rhs.value;
}
friend bool operator==( Foo lhs, Foo rhs ) {
return lhs.value==rhs.value;
}
};
template<class T>
struct my_less : std::less<T> {};
// backwards sort:
template<>
struct my_less<Foo> {
bool operator()(Foo const& lhs, Foo const& rhs) const {
return rhs.value < lhs.value;
}
};
using special_order = tuple_order< my_less >;
and bob is your uncle (live example).
special_order
可以传递给 std::map
或 std::set
,它将对遇到的任何元组或对进行排序 my_less
以替换元素的默认顺序。