簡體   English   中英

std::sort() 的自定義元組比較器

[英]Custom tuple comparator for std::sort()

我有以下情況:我必須像這樣將幾個指針和一個標識符打包到一個元組中:

typedef tuple<unsigned*, unsigned*, unsigned*, unsigned> tuple_with_pointers_t;

在這里,我有三個指針和一個 id。 在其他情況下,我可能有更多或更少的指針,但最后一個將是 id。 請注意,我僅將unsigned*用作示例。 它可能是更復雜的對象。

現在,我想比較兩個這樣的元組的值。 即,我需要取消引用除最后一個之外的所有元組元素。 我們可以使用以下方法(在 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);
}

當我們直接比較兩個元組時,這種結構工作得很好。 現在,我想在std::sort()上使用lesser()作為仿函數。 但是,g++ 和 clang++ 都抱怨他們不能“無法推斷模板參數‘_Compare’”。 換句話說,我們需要將正確的模板 arguments 傳遞給 lesser。

我在這里嘗試了一些東西,但沒有成功:我們有三個模板參數,我不確定如何在這里使用元組中的_Elements 最好的策略是什么?

這是一些玩具代碼:

#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;
}

我感謝任何幫助,無論是使用 C++17 還是 C++20。 但我正在尋找最緊湊和優雅的方式來做到這一點。 如果可能的話,它也可以直接在sort()調用上使用 lambda function。

謝謝!

更新:

好的,我發現了一個有用的小技巧:

sort(all.begin(), all.end(),
     [](const auto &a, const auto &b) {
        return lesser(a, b);
     }
);

基本上,我們將其包裝成 lambda,因此編譯器可以推斷出類型。 但是,我們可以做得更好嗎?

謝謝

正如評論中所建議的,您可以將比較器添加到 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;
}

作為替代方案,您可以將unsigned*包裝在自定義指針類型中並為其提供比較器。 然后您可以使用元組的默認比較器,它按字典順序比較元素。

我個人更喜歡這種方法,因為代碼更具可讀性。 我不知道這是否會破壞您現有的代碼或需要進行大量重構。

#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;
}

我想我們可以用這個。 當然,我不知道你的元組可以更復雜。

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);
        }
        
    }
};

通過執行非“遞歸”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>{});
     });

模板 lambda 是 C++20。
如果沒有它,至少需要一個助手 function,因此它就像其他解決方案一樣,將 function 包裝在一個仿函數中。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM