【问题标题】: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?比较具有替代排序的自定义类型的 std::tuple(或 std::pair)。是否可以插入自定义小于/比较功能?
【发布时间】:2015-02-22 15:09:55
【问题描述】:

问题

我有一个自定义类型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&lt; 没有为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&lt;(区分大小写),我们如何将std::pair&lt;A, int&gt; 与这种替代排序进行比较?

我知道为std::pair&lt;A, int&gt; 添加一个专门的operator&lt; 版本可以解决问题:

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&lt;std::tuple&lt;A, int, int&gt;std::tuple&lt;int, A, int&gt;std::tuple&lt;int, int, A&gt;, std::tuple&lt;A, A, int&gt;, ...(这甚至不是一个实用的方法!)

将编写好的内置operator&lt; 重新用于std::tuple 并将我的less-than 插入struct A 将是我想要的。可能吗?提前致谢!

【问题讨论】:

  • 编写一个自定义比较函数来做你想做的事情相对简单。你真的想要&lt; 语法吗?
  • 嗯,对于std::pair&lt;A, int&gt;(或std::tuple&lt;A, int, int&gt;),函数不需要命名为operator&lt;,可以使用其他名称,只要std::pair(或std::tuple) ) 可以用作std::map 的键,使用接受比较函数作为参数的构造函数。但我不想(全部手动)为std::tuple&lt;A, int, int&gt; 编写一个less-than 函数,为std::tuple&lt;int, A, int&gt; 编写另一个less-than 函数等等。正如我在上面的问题中提到的那样,我想为std::pair(或std::tuple)重用默认的less-than

标签: c++ c++11 operator-overloading std-pair stdtuple


【解决方案1】:

简单的方法是手动编写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&lt;const T, ...&gt;的元组,这将改变顺序分别遵循LE

下一个类型与std::less&lt;void&gt;std::less&lt;T&gt; 所做的类似——它采用特定类型的无状态排序函数模板对象,并使其成为类型通用的无状态排序函数对象:

// 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&lt;class T&gt;class Z 使得Z&lt;T&gt;Ts 上的一个排序,那么上面给出了一个对任何事物的通用排序。

下一个是我的最爱。它接受类型 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::mapstd::set,它将对遇到my_less 的任何元组或对进行排序,替换元素的默认顺序。

【讨论】:

  • 感谢您的回答!这太酷了!我想我必须花一些时间来消化(然后接受)你的解决方案。据我所知,typename X 是要比较的类型,typename L 似乎是 less-than 函子,typename E 似乎是 equals 函子。我还在消化其他部分。恐怕你在operator== 中打错字了(你写了operator=-)。
  • @siu 如果这是唯一的错字,请认为自己很幸运!这个想法是将 element-wise less 投影到元组的 less 代码上(reorder ref 部分将元组映射到使用不同元素的元组),然后使用该投影对元组进行排序(使用 order by,它需要一个顺序在 A 上和从 B 到 A 的地图并在 B 上生成订单。
  • 哇!听起来很复杂!您的意思是:1. 我必须手动为std::tuple&lt;A, int, char&gt; 编写比较函数。 2. 您的代码自动将std::tuple&lt;int, A, char&gt;std::tuple&lt;int, char, A&gt;(等)映射到std::tuple&lt;A, int, char&gt; 以进行比较?那么它会按声明的顺序比较元素吗?它也适用于std::tuple&lt;A, int, A&gt; 吗?抱歉,我仍在弄清楚您的代码是如何工作的。谢谢!
  • @siu 没有。重新排序(如果你完成它 - 那里有一个很大的待办事项)进行元素比较,并将元组映射到按元素比较排序的元组。大致需要tuple&lt;Foo,int&gt;tuple&lt;reorder_ref&lt;Foo&gt;,reorder_ref&lt;int&gt;&gt;(实际上更多const&amp;s 和my_less 类型等)。输出元组上的&lt; 做了正确的事情。 order_by 使用它直接订购任何元组。
  • 现在,我正在向后阅读代码。 no_case 是在地图中充当 operator&lt; 的函子。 no_case 等同于 order_by&lt;reorder_t&lt;my_less, my_equals&gt;&gt;(你写的是 reorder 而不是 reorder_t,但我想这是一个错字)。 order_by&lt;O, L&gt; 接受 2 个模板参数(一个是可选的)。它可以通过在TU 上调用L 来比较任何TU,并将其转换为O。现在,O 指的是reorder_t&lt;my_less, my_equals&gt;L 指的是std::less&lt;&gt;。我猜reorder_t 是您所指的“大待办事项”。我看不出它是如何工作的。您能否添加更多解释?
猜你喜欢
  • 2011-05-10
  • 1970-01-01
  • 2016-10-22
  • 2020-08-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-12
相关资源
最近更新 更多