【问题标题】:can a std::tuple be sorted at compilte-time/run-time depending on its values可以在编译时/运行时根据其值对 std::tuple 进行排序吗
【发布时间】:2020-04-21 06:35:06
【问题描述】:

我想知道 constexpr std::tuple 是否可以在编译时排序:

template<typename T>
struct A{ T val; }; // a constexpr-enabled class

constexpr auto t = std::make_tuple( A<int>{3}, A<float>{1.f}, A<double>{2.0});
constexpr auto s = sort(t, [](auto&& v){return v.val; });

static_assert(std::is_same_v<std::tuple<A<float>,
                                        A<double>, 
                                        A<int>>,decltype(s)>, "Wups");

这可能吗,这里需要哪些构建块(std::sortconstexpr)?

(据我所知)在运行时不可能做到这一点,因为无法在运行时确定已排序元组的类型。如果要构建所有排列的类型映射,我也看不到一种方法,实际的排列仍然只在运行时才知道。

回复https://stackoverflow.com/a/46006026/293195 给出了一些提示,它应该在编译时工作,但是如何?

【问题讨论】:

  • 是的,已更正。
  • std::sort 仅适用于同类容器。需要手动编写排序函数。
  • 原则上你可以。没有任何东西可以阻止这样的事情发生。但这不是标准库提供的功能。
  • 你说得对,在运行时不可能这样做,因为所有类型信息都需要在编译时解析。 s 的类型必须在编译时完全确定。

标签: c++ sorting constexpr c++20 stdtuple


【解决方案1】:

这是一个使用Boost.Mp11 的好的解决方案:

template <auto F>
struct quote {
    template <typename... T>
    using fn = decltype(F(T{}...));
};

constexpr auto sort_t() {
    // predicate used for sorting
    using pred = quote<[](auto I, auto J){
        return mp_bool<(std::get<I>(t).val < std::get<J>(t).val)>{};
    }>;

    // this gives us an mp_list of the indices into 't', sorted
    using indices = mp_sort<
        mp_iota<mp_int<std::tuple_size_v<decltype(t)>>>,
        pred::fn>;

    // turn those indices into a new tuple
    return []<typename... Ts>(mp_list<Ts...>){
        return std::tuple(std::get<Ts::value>(t)...);
    }(indices{});
}

constexpr auto s = sort_t();

最后一步可能会清理一下。


请注意,理想情况下,这会将 t 作为非类型模板参数,但我们可能要到 C++23 才能做到这一点:

template <auto Tuple>
constexpr auto sort_tuple() { /* ... */ }

constexpr auto s = sort_tuple<t>();

虽然实际上它可以采用auto const&amp; 非类型模板参数,这在这里已经足够了:

template <auto const& in>
constexpr auto sort_tuple() {
    using pred = quote<[](auto I, auto J){
        return mp_bool<(std::get<I>(in).val < std::get<J>(in).val)>{};
    }>;
    using indices = mp_sort_q<
        mp_iota_c<std::tuple_size_v<std::decay_t<decltype(in)>>>,
        pred>;

    return []<typename... Ts>(mp_list<Ts...>){
        return std::tuple(std::get<Ts::value>(in)...);
    }(indices{});
}
constexpr auto s = sort_tuple<t>();

【讨论】:

  • 您的链接已损坏。你知道mp_sort 在平均实例化O(N ln N) 模板的意义上是否真的有效吗?
  • ... 其中N 是元组中不同类型的数量。
  • @walnut 很奇怪,不知道l 是怎么被砍掉的。修复了链接。是的,我认为它会很有效,图书馆里的其他东西都是。
  • 哇,这真是太棒了:)!我不明白的是:为什么通过非类型模板参数传递。为什么对元组的引用不起作用?因为它不是一个常量。表达式?
  • @Gabriel 是的,整个元组必须是一个常量表达式。
【解决方案2】:

您可以使用标准库的 std::sort 算法(如果它是 constexpr,因为它应该在 C++20 中),即使它通过仅将元组的索引包装为不同的索引来期望同质类型输入std::variants 数组并对其进行排序。

然后,您可以提供所需的任何谓词,只要可以将元组中的每对类型与谓词进行比较,并且满足比较器与std::sort 的通常要求。

特别是有一个std::less&lt;void&gt; 专门化,它接受两种任意类型以与&lt; 进行比较,所以我使用它作为默认值,类似于std::sort 所做的。

正如@Barry 所提到的,您需要将元组作为非类型模板参数来访问函数内部的值。如果您还想在排序算法中访问谓词的状态,那么您还需要将其作为引用非类型模板参数传递。

还请注意,由于需要比较器的实例化和访问次数,这可能会花费元组大小的二次编译时间。对于大型元组,您可能可以使用模板元编程来实现不需要二次多次实例化的自己的算法,而模板元编程只会实例化所需的比较。

或者,可以通过将元组中包含的值(或std::refs 到值)复制到std::variants 中来减少所需实例化的数量,而不是在删除重复类型后减少所需实例化的数量并在 distinct 类型的数量上以二次方的方式访问。请参阅此答案的历史,以了解实际上并未对类型进行重复数据删除的不完整实现,因此元组的大小可能仍为二次方。

namespace detail {
    template<auto& t, typename Pred, auto... Is>
    constexpr auto sort(Pred&& pred, std::index_sequence<Is...>) {
        constexpr auto sorted_indices = [&]{
            using var_t = std::variant<std::integral_constant<std::size_t, Is>...>;

            std::array indices{var_t{std::in_place_index<Is>}...};

            std::sort(indices.begin(), indices.end(), [&](auto x, auto y){
                return std::visit([&](auto a, auto b) {
                    return pred(std::get<decltype(a)::value>(t), std::get<decltype(b)::value>(t));
                }, x, y);
            });

            return indices;
        }();

        using old_tuple_t = std::remove_cvref_t<decltype(t)>;
        using new_tuple_t = std::tuple<std::tuple_element_t<sorted_indices[Is].index(), old_tuple_t>...>;
        return new_tuple_t{std::get<sorted_indices[Is].index()>(t)...};
    }
}

template<auto& t, typename Pred = std::less<void>>
constexpr auto sort(Pred&& pred = {}) {
    using size = std::tuple_size<std::remove_cvref_t<decltype(t)>>;
    return detail::sort<t>(std::forward<Pred>(pred), std::make_index_sequence<size::value>{});
}

测试(从问题中略微更正,包括一些重复的类型):

template<typename T>
struct A{ T val; }; // a constexpr-enabled class

// Test case 1 comparing A's val members as predicate
constexpr auto t = std::make_tuple( A<int>{10}, A<float>{1.f}, A<double>{5.0}, A<int>{3});
constexpr auto s = sort<t>([](auto&& v, auto&& w){ return v.val < w.val; });
static_assert(std::is_same_v<const std::tuple<A<float>, A<int>, A<double>, A<int>>, decltype(s)>);

// Test case 2 using default `std::less<void>` for comparison
constexpr auto t2 = std::make_tuple(10, 1.f, 5.0, 3);
constexpr auto s2 = sort<t2>();
static_assert(std::is_same_v<const std::tuple<float, int, double, int>, decltype(s2)>);

this godbolt 的 GCC 主干上测试。它不适用于带有 libstdc++ 或 libc++ 的 Clang 主干。从产生的错误消息来看,我认为这是因为它还没有实现 constexpr std::sort

【讨论】:

    【解决方案3】:

    这里(为了完整起见)基于@walnut@Barry 的优秀答案的混合解决方案

    Live on Wandbox

    #include <type_traits>
    #include <tuple>
    #include "meta.hpp"
    
    namespace details
    {
        template<auto F>
        struct lambdaQuote
        {
            template<typename... T>
            using invoke = decltype(F(T{}...));
        };
    }
    
    template<auto& tuple, auto pred, bool doForward = false>
    constexpr auto sort()
    {
        using namespace meta;
        using namespace details;
        using Tuple = std::remove_cvref_t<decltype(tuple)>;
    
        using Pred = lambdaQuote<
            [](auto I, auto J) {
            return bool_<pred(std::get<I>(tuple), std::get<J>(tuple))>{};
        }>;
    
        using Indices       = as_list<make_index_sequence<std::tuple_size_v<Tuple>>>;
        using SortedIndices = meta::sort<Indices, Pred>;
        auto createTuple    = []<typename... Ts>(list<Ts...>)
        {
            if constexpr(doForward)
            {
                return std::forward_as_tuple(std::get<Ts::value>(tuple)...);
            }
            else
            {
                return std::make_tuple(std::get<Ts::value>(tuple)...);
            }
        };
    
        return createTuple(SortedIndices{});
    }
    
    template<typename T>
    struct A
    {
        constexpr A(T t) : val(t){};
        T val;
    private:
        constexpr A() = default;
    };
    
    
    int main()
    {
        static constexpr auto t = std::make_tuple(A{10.0},A{9},A{8.0f},A{7.0},A{6.0},A{5});
        constexpr auto p        = [](auto&& a, auto&& b) { return a.val < b.val; };
        constexpr auto s        = sort<t, p>();
    
        constexpr auto tRes = std::make_tuple(A{5},A{6.0},A{7.0},A{8.0f},A{9},A{10.0});
        static_assert(std::is_same_v<decltype(s), decltype(tRes)>, "not correct");
        static_assert(std::get<0>(tRes).val == std::get<0>(s).val, "not correct");
    }
    

    【讨论】:

      猜你喜欢
      • 2011-06-06
      • 2023-03-07
      • 2017-12-24
      • 2015-05-29
      • 2021-11-19
      • 2021-01-21
      • 2012-11-14
      • 2014-01-14
      • 1970-01-01
      相关资源
      最近更新 更多