[英]Custom tuple comparator for std::sort()
I have the following situation: I must pack several pointers and an identifier into a tuple like this:我有以下情况:我必须像这样将几个指针和一个标识符打包到一个元组中:
typedef tuple<unsigned*, unsigned*, unsigned*, unsigned> tuple_with_pointers_t;
Here, I have three pointers and one id.在这里,我有三个指针和一个 id。 In other situations, I may have more or fewer pointers, but the last will be the id.在其他情况下,我可能有更多或更少的指针,但最后一个将是 id。 Note that I used unsigned*
as an example only.请注意,我仅将unsigned*
用作示例。 It could be more complex objects.它可能是更复杂的对象。
Now, I want to compare the values of two such tuples.现在,我想比较两个这样的元组的值。 Ie, I need to dereference all tuple elements but the last.即,我需要取消引用除最后一个之外的所有元组元素。 We can archive this using the following (in C++17):我们可以使用以下方法(在 C++17 中)将其存档:
template <size_t I = 0, typename T, typename... Ts>
constexpr bool lesser(std::tuple<T, Ts...> a, std::tuple<T, Ts...> b)
{
if constexpr (I < sizeof...(Ts))
return (*std::get<I>(a) < *std::get<I>(b)) ||
((*std::get<I>(a) == *std::get<I>(b)) && lesser<I + 1>(a, b));
else
return std::get<I>(a) < std::get<I>(b);
}
Such construct works very fine when we compare two tuples directly.当我们直接比较两个元组时,这种结构工作得很好。 Now, I would like to use lesser()
as a functor on the std::sort()
.现在,我想在std::sort()
上使用lesser()
作为仿函数。 But, both g++ and clang++ complain that they cannot "couldn't infer template argument '_Compare'".但是,g++ 和 clang++ 都抱怨他们不能“无法推断模板参数‘_Compare’”。 In other words, we need to pass the correct template arguments to lesser.换句话说,我们需要将正确的模板 arguments 传递给 lesser。
I have tried some things here, but with no success: we have three template parameters, and I am not sure how I can use the _Elements
from the tuple here.我在这里尝试了一些东西,但没有成功:我们有三个模板参数,我不确定如何在这里使用元组中的_Elements
。 What will be the best strategy?最好的策略是什么?
Here is some toy code:这是一些玩具代码:
#include <algorithm>
#include <iostream>
#include <tuple>
#include <vector>
using namespace std;
// My weird tuple with pointers and one unsigned index.
typedef tuple<unsigned*, unsigned*, unsigned*, unsigned> tuple_with_pointers_t;
// This works fine for two tuples directly. Note that we cannot dereference
// the last tuple element, so we compare it directly.
template <size_t I = 0, typename T, typename... Ts>
constexpr bool lesser(std::tuple<T, Ts...> a, std::tuple<T, Ts...> b)
{
if constexpr (I < sizeof...(Ts))
return (*std::get<I>(a) < *std::get<I>(b)) ||
((*std::get<I>(a) == *std::get<I>(b)) && lesser<I + 1>(a, b));
else
return std::get<I>(a) < std::get<I>(b);
}
int main() {
// Three sets of values.
vector<unsigned> values1 {1, 2, 3};
vector<unsigned> values2 {10, 20, 30};
vector<unsigned> values3 {11, 22, 33};
// Here, we pack it all together with the index.
vector<tuple_with_pointers_t> all;
for(unsigned i = 0; i < values1.size(); ++i)
all.emplace_back(&values1[i], &values2[i], &values3[i], i);
// So, it works if we want to compare two elements of our vector.
cout << "\n- t0 < t1: " << std::boolalpha << lesser(all[0], all[1]);
cout << "\n- t2 < t1: " << std::boolalpha << lesser(all[2], all[1]);
// Now, I want to sort the tuples by their values. The compiler doesn't
// like it: it cannot deduce the template parameters.
sort(all.begin(), all.end(), lesser);
return 0;
}
I appreciate any help, either using C++17 or C++20.我感谢任何帮助,无论是使用 C++17 还是 C++20。 But I'm looking for the most compact and elegant way to do it.但我正在寻找最紧凑和优雅的方式来做到这一点。 It could be using a lambda function directly on the sort()
call, too, if possible.如果可能的话,它也可以直接在sort()
调用上使用 lambda function。
Thanks!谢谢!
Update:更新:
OK, I found a little hack that works:好的,我发现了一个有用的小技巧:
sort(all.begin(), all.end(),
[](const auto &a, const auto &b) {
return lesser(a, b);
}
);
Basically, we wrap it into a lambda, and therefore the compiler can deduce the types.基本上,我们将其包装成 lambda,因此编译器可以推断出类型。 But, can we do better?但是,我们可以做得更好吗?
Thanks谢谢
As suggested in the comments, you can add your comparator into a function object and pass an instance of the object to sort
:正如评论中所建议的,您可以将比较器添加到 function object 并将 object 的实例传递给sort
:
#include <algorithm>
#include <iostream>
#include <tuple>
#include <vector>
using namespace std;
// My weird tuple with pointers and one unsigned index.
typedef tuple<unsigned*, unsigned*, unsigned*, unsigned> tuple_with_pointers_t;
namespace details {
template <size_t I = 0, typename T, typename... Ts>
constexpr bool lesser(std::tuple<T, Ts...> const& a, std::tuple<T, Ts...> const& b)
{
if constexpr (I < sizeof...(Ts))
return (*std::get<I>(a) < *std::get<I>(b)) ||
((*std::get<I>(a) == *std::get<I>(b)) && lesser<I + 1>(a, b));
else
return std::get<I>(a) < std::get<I>(b);
}
}
struct Less
{
template <typename... Ts>
constexpr bool operator()(std::tuple<Ts...> const& a, std::tuple<Ts...> const& b)
{
return details::lesser<0, Ts...>(a, b);
}
};
int main() {
// Three sets of values.
vector<unsigned> values1 {1, 2, 3};
vector<unsigned> values2 {10, 20, 30};
vector<unsigned> values3 {11, 22, 33};
// Here, we pack it all together with the index.
vector<tuple_with_pointers_t> all;
for(unsigned i = 0; i < values1.size(); ++i)
all.emplace_back(&values1[i], &values2[i], &values3[i], i);
// So, it works if we want to compare two elements of our vector.
cout << "\n- t0 < t1: " << std::boolalpha << Less()(all[0], all[1]);
cout << "\n- t2 < t1: " << std::boolalpha << Less()(all[2], all[1]);
// Now, I want to sort the tuples by their values. The compiler doesn't
// like it: it cannot deduce the template parameters.
sort(all.begin(), all.end(), Less());
return 0;
}
As an alternative, you could wrap your unsigned*
in a custom pointer type and provide a comparator for it.作为替代方案,您可以将unsigned*
包装在自定义指针类型中并为其提供比较器。 Then you can use the default comperator for tuples, which compares the elements lexicographically.然后您可以使用元组的默认比较器,它按字典顺序比较元素。
I personally would prefer this approach, because the code is much more readable.我个人更喜欢这种方法,因为代码更具可读性。 I don't know if this would break your existing code or would entail a huge refactor.我不知道这是否会破坏您现有的代码或需要进行大量重构。
#include <algorithm>
#include <iostream>
#include <tuple>
#include <vector>
using namespace std;
class Ptr
{
public:
Ptr(unsigned& v) : m_ptr(&v) {}
unsigned operator*() const {
return *m_ptr;
}
private:
unsigned* m_ptr;
};
bool operator<(Ptr const& l, Ptr const& r)
{
return *l < *r;
}
// My weird tuple with pointers and one unsigned index.
typedef tuple<Ptr, Ptr, Ptr, unsigned> tuple_with_pointers_t;
int main() {
// Three sets of values.
vector<unsigned> values1 {1, 2, 3};
vector<unsigned> values2 {10, 20, 30};
vector<unsigned> values3 {11, 22, 33};
// Here, we pack it all together with the index.
vector<tuple_with_pointers_t> all;
for(unsigned i = 0; i < values1.size(); ++i)
all.emplace_back(values1[i], values2[i], values3[i], i);
// So, it works if we want to compare two elements of our vector.
cout << "\n- t0 < t1: " << std::boolalpha << (all[0] < all[1]);
cout << "\n- t2 < t1: " << std::boolalpha << (all[2] < all[1]);
sort(all.begin(), all.end());
return 0;
}
I think we can use this.我想我们可以用这个。 Of course, I don't know your tuple can be more complex.当然,我不知道你的元组可以更复杂。
template<typename T, size_t I = 0>
using type_tuple = typename std::tuple_element<I,T>::type;
template<size_t I = 0, template<typename> class F = std::less_equal>
struct TupleCompare
{
template<typename T>
bool operator()(T const &t1, T const &t2){
using _type = typename std::conditional<std::is_pointer<type_tuple<T>>::value,
typename std::remove_pointer<type_tuple<T,I>>::type, type_tuple<T>>::type;
if constexpr (I == std::tuple_size_v<T> - 1) {
return F<_type>()(std::get<I>(t1), std::get<I>(t2));
} else {
return F<_type>()(*std::get<I>(t1), *std::get<I>(t2)) && TupleCompare<I+1, F>()(t1, t2);
}
}
};
By doing a non-"recursive" function, you might do a "one-liner":通过执行非“递归”function,您可以执行“单行”:
sort(all.begin(), all.end(),
[]<typename T>(const T& lhs, const T& rhs) {
return [&]<std::size_t... Is>(std::index_sequence<Is...>){
return std::tie(std::get<Is>(lhs)...)
< std::tie(std::get<Is>(rhs)...);
}(std::make_index_sequence<std::tuple_size_v<T> - 1>{});
});
template lambda are C++20.模板 lambda 是 C++20。
Without that, at least an helper function is required, so it becomes, as the other solutions, wrapping a function in a functor.如果没有它,至少需要一个助手 function,因此它就像其他解决方案一样,将 function 包装在一个仿函数中。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.