【问题标题】:Make integer sequence unique at compile time在编译时使整数序列唯一
【发布时间】:2021-05-25 20:58:55
【问题描述】:

假设我有:

template<int... N>
class seq
{
};

template<int... N>
struct uniq{
    using type = seq<N...>;
};

我需要以某种方式使序列独一无二,以便

std::is_same_v<uniq<1,2,2,2,3,3,3>::type, seq<1, 2, 3>>; 

最终是真实的。换句话说,使序列唯一,然后创建一个序列。
有没有办法在编译时实现这一点?

【问题讨论】:

  • 你的模板参数会一直排序吗?

标签: c++ templates c++17 template-meta-programming


【解决方案1】:

使用std

使用标准库中的&lt;type_traits&gt;,您可以像这样实现自己的:

#include <type_traits>

namespace detail
{
template<class, auto... Ns>
struct uniq_impl;
template<template<auto...> class T, auto... Ms, auto N, auto... Ns>
struct uniq_impl<T<Ms...>, N, Ns...> : std::conditional_t<
    (... || (N == Ms)),
    uniq_impl<T<Ms...>, Ns...>,
    uniq_impl<T<Ms..., N>, Ns...>>
{
};
template<template<auto...> class T, auto... Ms>
struct uniq_impl<T<Ms...>>
{
    using type = T<Ms...>;
};
} // namespace detail

template<int... Ns>
class seq
{
};

template<int... Ns>
using uniq = detail::uniq_impl<seq<>, Ns...>;

static_assert(std::is_same_v<typename uniq<1,2,2,2,3,3,3>::type, seq<1, 2, 3>>);

uniq_impl 从空的seq&lt;&gt;auto... Ns 的参数包开始工作,然后使用模板特化一次一个地取出参数包的前面

template<template<auto...> class T, auto... Ms, auto N, auto... Ns>
struct uniq_impl<T<Ms...>, N, Ns...> : std::conditional_t<
    (... || (N == Ms)),
    uniq_impl<T<Ms...>, Ns...>,
    uniq_impl<T<Ms..., N>, Ns...>>
{
};

它使用fold expression 检查N 是否在auto... Ms 的集合中,并决定是否将N 推送到Ms 或使用std::conditional_t 丢弃它。一旦auto... Ns 为空,它就会使用特化

template<template<auto...> class T, auto... Ms>
struct uniq_impl<T<Ms...>>
{
    using type = T<Ms...>;
};

标记生成的唯一值容器。在 godbolt.org 上试用:Demo

使用boost::mp11

作为others have pointed out,您可以将算法委托给boost::mp11::mp_unique,但由于it works for types and not values,您需要在std::integral_constant 之间封装和解封装值才能使用此方法:

#include <boost/mp11/algorithm.hpp>

namespace detail
{
template<template<auto...> class T, auto... Ns>
class uniq_impl
{
    static boost::mp11::mp_list<std::integral_constant<decltype(Ns), Ns>...> types();

    template <class L>
    static boost::mp11::mp_unique<L> transform(L);

    template<class... Ts, auto... Ms>
    static T<Ms...> values(boost::mp11::mp_list<std::integral_constant<Ts, Ms>...>);

public:
    using type = decltype(values(transform(types()))); 
};
} // namespace detail

template<int... Ns>
class seq
{
};

template<int... Ns>
using uniq = detail::uniq_impl<seq, Ns...>;

static_assert(std::is_same_v<typename uniq<1,2,2,2,3,3,3>::type, seq<1, 2, 3>>);

在 godbolt.org 上试用:Demo

【讨论】:

  • 这是一些非常漂亮的元编程的一个非常简洁和富有表现力的例子。它也适用于未排序的整数序列。
【解决方案2】:

您可以为此使用boost::mp11::mp_unique

Example:

#include <boost/mp11.hpp>

namespace
{
template <int... N>
using seq = boost::mp11::mp_list_c<int, N...>;

template <int... N>
struct uniq
{
    using type = boost::mp11::mp_unique<seq<N...>>;
};
}

int main()
{
    static_assert(std::is_same_v<uniq<1,2,2,2,3,3,3>::type, seq<1,2,3>>);
    static_assert(std::is_same_v<uniq<4,1,9,9,2,2,3,1,5>::type, seq<4,1,9,2,3,5>>);
    return 0;
}

如果别名不适合seq,您可以执行this 之类的操作:

template <int... N>
struct seq
{};

template <int... N>
struct uniq
{
private:
    template <int... Is>
    static constexpr auto uniquer(boost::mp11::mp_list_c<int, Is...>) -> seq<Is...>;

public:
    using type = decltype(uniquer(boost::mp11::mp_unique<boost::mp11::mp_list_c<int, N...>>{}));
};

【讨论】:

  • 我认为这适用于类型而不是整数序列。 godbolt.org/z/7dd5zj
  • @MarekR 整数序列可以是类型。我会更新我的答案。
  • @PatrickRoberts 你是对的,它变得有点复杂 - 我已经更新了我的答案。
【解决方案3】:

要删除相邻的重复项(如std::unique),您可以这样做:

template <typename Seq, typename Res = std::index_sequence<>>
struct reverse;

template <typename Res>
struct reverse<std::index_sequence<>, Res>
{
    using type = Res;
};

template <std::size_t I, std::size_t ... Is, std::size_t ... Js>
struct reverse<std::index_sequence<I, Is...>, std::index_sequence<Js...>> : reverse<std::index_sequence<Is...>, std::index_sequence<I, Js...>>
{};

template <typename Seq, typename Res = std::index_sequence<>>
struct uniq;

template <typename Res>
struct uniq<std::index_sequence<>, Res>
{
    using type = typename reverse<Res>::type;    
};

template <std::size_t I, std::size_t ... Is, std::size_t ... Js>
struct uniq<std::index_sequence<I, Is...>, std::index_sequence<I, Js...>> : uniq<std::index_sequence<Is...>, std::index_sequence<I, Js...>> {};

template <std::size_t I, std::size_t ... Is, std::size_t ... Js>
struct uniq<std::index_sequence<I, Is...>, std::index_sequence<Js...>> : uniq<std::index_sequence<Is...>, std::index_sequence<I, Js...>> {};

static_assert(std::is_same_v<reverse<std::index_sequence<3, 2, 1>>::type, std::index_sequence<1, 2, 3>>); 
static_assert(std::is_same_v<uniq<std::index_sequence<1,2,2,2,3,3,3>>::type, std::index_sequence<1, 2, 3>>);

Demo

在 C++20 中,一些算法甚至变成了 constexpr 并允许:

template <std::size_t ... Is, std::size_t ... Js>
consteval auto unique_impl(std::index_sequence<Is...>, std::index_sequence<Js...>)
{
    constexpr std::array<std::size_t, sizeof...(Is)> arr = [](){
        std::array<std::size_t, sizeof...(Is)> arr{{Is...}};
        //std::sort(arr.begin(), arr.end());
        std::unique(arr.begin(), arr.end());
        return arr;
    }();

    return std::index_sequence<arr[Js]...>{};
}

template <std::size_t ... Is>
consteval auto unique_impl(std::index_sequence<Is...> seq)
{
    constexpr std::size_t size = [](){
        std::array<std::size_t, sizeof...(Is)> arr{{Is...}};
        //std::sort(arr.begin(), arr.end());
        auto it = std::unique(arr.begin(), arr.end());
        return std::distance(arr.begin(), it);
    }();
    return unique_impl(seq, std::make_index_sequence<size>());
}

template <std::size_t ... Is>
using unique = decltype(unique_impl(std::index_sequence<Is...>{}));

static_assert(std::is_same_v<unique<1,2,2,2,3,3,3>, std::index_sequence<1, 2, 3>>);

Demo

注意:constexpr std::vector 通常甚至允许删除 lambda 中的重复代码。

【讨论】:

  • 不错。不过,最好指出这需要对原始包装进行分类; static_assert(std::is_same_v&lt;uniq&lt;std::index_sequence&lt;1,3,2,2,2,3,3,3&gt;&gt;::type, std::index_sequence&lt;1, 3, 2&gt;&gt;); 失败(解析为 std::index_sequence&lt;1, 3, 2, 3&gt;)。
  • @dfrib:类似于(运行时)std::unique
  • 排序也是 C++20 中的 constexpr :)
【解决方案4】:

使用 C++20,仅 STL

我在 gcl 库中使用了类似的东西。
(请注意,如果您的编译器允许 lambda,您可以用 IIFE lambda 替换 deduplicate_values在未评估的上下文中

此处的示例:https://godbolt.org/z/KzjWrM

template <auto ... values>
struct sequence{};

template <auto ... values>
constexpr auto deduplicate_values()
{
    constexpr auto arguments = std::tuple{values...};
    constexpr auto element_if_predicate = [arguments]<auto argument, std::size_t argument_index>() consteval {
        constexpr auto has_previous_position = [&arguments]<std::size_t ... indexes>(std::index_sequence<indexes...>) consteval {
            return (((std::get<indexes>(arguments) == argument) || ...));
        }(std::make_index_sequence<argument_index>{});
        if constexpr (has_previous_position)
            return std::tuple{};
        else return std::tuple{argument};
    };

    constexpr auto deduplicated_values_as_tuple = [&arguments, element_if_predicate]<std::size_t ... indexes>(std::index_sequence<indexes...>) consteval {
        return std::tuple_cat(element_if_predicate.template operator()<std::get<indexes>(arguments), indexes>()...);
    }(std::make_index_sequence<std::tuple_size_v<decltype(arguments)>>{});

    return [&deduplicated_values_as_tuple]<std::size_t ... indexes>(std::index_sequence<indexes...>) consteval {
        return sequence<std::get<indexes>(deduplicated_values_as_tuple)...>{};
    }(std::make_index_sequence<std::tuple_size_v<decltype(deduplicated_values_as_tuple)>>{});
}

template <auto ... values>
using deduplicated_sequence = decltype(deduplicate_values<values...>());

static_assert(std::is_same_v<sequence<1,2,3>, deduplicated_sequence<1,2,3,1,2,3>>);

解释:

  • 将值包装到元组中
  • 创建一个谓词来检查values... 中是否存在先前的值
    • 失败时返回一个空的std::tuple
    • 成功返回std::tuple{value}
  • values... 扩展为std::tuple_cat(predicate(...))
  • 将 std::tuple 元素重新打包成你想要的类型,sequence

gcl库代码:

template <auto ... values>
constexpr auto tuple_erase_duplicate_values()
{
    constexpr auto arguments = std::tuple{values...};

    constexpr auto element_if_predicate = [arguments]<auto argument, std::size_t argument_index>() consteval {
        constexpr auto has_previous_position = [&arguments]<std::size_t ... indexes>(std::index_sequence<indexes...>) consteval {
            return (((std::get<indexes>(arguments) == argument) || ...));
        }(std::make_index_sequence<argument_index>{});

        if constexpr (has_previous_position)
            return std::tuple{};
        else return std::tuple{argument};
    };

    return [&arguments, element_if_predicate]<std::size_t ... indexes>(std::index_sequence<indexes...>) consteval {
        return std::tuple_cat(element_if_predicate.template operator()<std::get<indexes>(arguments), indexes>()...);
    }(std::make_index_sequence<std::tuple_size_v<decltype(arguments)>>{});
}
static_assert(tuple_erase_duplicate_values<'a', 'b', 'a', 1, 2, 1>() == std::tuple{'a', 'b', 1, 2});

在我自己看来,它不那么神秘,因此更容易阅读/维护;而剩余的编译时间。
此外,它还避免了递归。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-01-23
    • 2017-12-29
    • 1970-01-01
    • 2012-04-14
    • 1970-01-01
    • 2010-11-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多