简体   繁体   English

std::sort() 的自定义元组比较器

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM