在编译时使整数序列唯一
假设我有:
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>>;
最终是真的。换句话说,使序列唯一,然后创建一个 seq。
有没有办法在编译时实现这一点?
回答
使用 std
使用<type_traits>标准库,您可以像这样实现自己的:
#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<>的参数包开始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...>>
{
};
它检查是否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 上试用:演示。
使用 boost::mp11
正如其他人指出的那样,您可以将算法委托给boost::mp11::mp_unique,但因为它适用于类型而不是值,您需要将值包装和解开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 上试用:演示。
- That is a very succinct and expressive example of some very pretty meta-programming. And it works for unsorted integer sequences too to boot.
回答
您可以为此使用boost::mp11::mp_unique。
示例:
#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,您可以执行以下操作:
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...>>{}));
};