用于基于成员字段或函数创建比较器的快捷方式

Shortcut for creating a comparator based on a member field or function

本文关键字:函数 创建 比较器 快捷方式 字段 于成员 成员 用于      更新时间:2023-10-16

我经常发现自己想为structclass创建一个比较器对象,它只是提取类的一个成员并对其进行通常的<比较。

例如:

struct student {
int id;
std::string name;
};
// sort by ID
std::sort(students.begin(), students.end(), [](const student& l, const student& r){ return l.id < r.id; });

那里有很多样板文件,特别是因为我们必须重复声明lr.标准库中有没有办法基于"提取器"函数创建比较器,该函数返回要比较的对象?

像这样:

std::sort(students.begin(), students.end(), compare_on([](const student& s){ return s.id; });

我正在使用 C++11,但如果更高标准中有不适用于 C++11 的解决方案,我也有兴趣(所以我可以在我的"升级原因"列表中添加一些内容(。

我在这里询问的是使用单个成员作为压缩包,以及默认的比较"小于",但对于易于撰写的技术,例如允许您按字典顺序使用两个字段,或更改比较运算符。

您实际上正在寻找的是允许将投影传递到算法中。N4128 为标准库提出了这个建议,C++20 将为许多算法提供这些。

但在那之前,我们可以自己做这件事。编写一个新的重载sort

struct identity {
template <typename T>
T&& operator()(T&& t) const noexcept { return std::forward<T>(t); }
};
// because no std::less<> in C++11 yet
struct less {
template <typename T, typename U>
constexpr bool operator()(T const& lhs, U const& rhs) const {
return lhs < rhs;
}
};
template <typename Range, typename Comp=less, typename Proj=identity>
void sort_proj(Range& range, Comp comp={}, Proj proj={}) {
using std::begin;
using std::end;
auto first = begin(range), last = end(range);
using reference = typename std::iterator_traits<decltype(first)>::reference;
std::sort(first, last,
[&](reference lhs, reference rhs) {
return comp(std::ref(proj)(lhs), std::ref(proj)(rhs));
});
}

std::ref(f)(x)是在 C++11 中获取INVOKE功能的技巧。它基本上允许您将指针作为投影传递给成员。此实现将允许您编写:

sort_proj(students, less{}, &student::id);

请注意,投影与排序无关。所以我可以很容易地做各种各样的事情:

sort_proj(students);                            // sort on students, if they're ordered
sort_proj(students, greater{}, &student::name); // decreasing, by name
sort_proj(students, less{},                     // by name, then id
[](student const& s) {
return std::tie(s.name, s.id);
});

这种方法对于从算法中消除大量样板非常有用。我有一个标题,里面充满了许多常用标准算法的基于投影的重载。

您可以定义实用程序类

template <class Fct> class compare_on {
public:
compare_on(Fct&& get) : get(std::forward<Fct>(get)) {}
template <class T> bool operator()(const T& lhs, const T& rhs)
{
return get(lhs) < get(rhs);
}
private:
Fct get;
};

然后像您描述的那样将其传递给std::sort(使用 C++17 类模板参数推导(

std::sort(students.begin(), students.end(),
compare_on([](const student& s){ return s.id; }));

截至 C++17 日,@Justin在评论中指出,实际比较可以改进(#include <functional>(,以便

return std::invoke(get, lhs) < std::invoke(get, rhs);

它允许使用数据成员引用进行实例化:

std::sort(students.begin(), students.end(), compare_on(&student::id));

当绑定到 C++11 时,忘记std::invoke并使用compare_on的显式实例化。后者不适合 lambda,因此通常的参数推导make_*助手:

template <class Fct> auto make_compare_on(Fct&& get)
-> decltype(compare_on<Fct>(std::forward<Fct>(get)))
{
return compare_on<Fct>(std::forward<Fct>(get));
}

请注意,您可以在 C++14 中删除尾随返回类型。

最后,这里的命名应该改进:compare_on具有误导性,因为它隐藏了函数对象的真正作用 - 通过operator <进行比较。也许compare_less_then或类似的东西会更好,或者添加另一个可以指定为标准谓词之一的模板参数(std::less等(。

根据您的建议,我提出这样的compare_on

template<class T>
auto compare_on(T &&t){
return [t](const auto &l, const auto &r){
return l.*t < r.*t; 
};
}
... 
std::sort(students.begin(), students.end(), compare_on(&student::id));

它需要C++14。它使用指向成员的指针来准确获取您要求的行为。

在 C++11 中,使用类似的想法,它看起来像这样:

template<class U>
class comparison{
public:
comparison(const U &ptr) : memberPtr(ptr){}
template<class T>
int operator()(const T &l, const T &r){
return l.*memberPtr < r.*memberPtr;
}
private:
U memberPtr;
};
template<class T>
comparison<T> compare_on(T &&t){
return comparison<T>(std::forward<T>(t));
}

正如@Barry所建议的,您应该将所有l.*t替换为std::invoke(t, l),以使其在 C++17 中更加通用。

range-v3 实现了投影

// sort by ID
ranges::sort(students, std::less<>{}, &student::id);