比较两组类型是否相等

时间:2017-02-27 14:40:15

标签: c++ c++11 templates variadic-templates template-meta-programming

如何检查两个参数包是否相同,忽略它们的内部顺序?

到目前为止,我只有框架(使用std::tuple),但没有功能。

#include <tuple>
#include <type_traits>

template <typename, typename>
struct type_set_eq : std::false_type
{
};

template <typename ... Types1, typename ... Types2>
struct type_set_eq<std::tuple<Types1...>, std::tuple<Types2...>>
    : std::true_type
{
    // Should only be true_type if the sets of types are equal
};

int main() {
    using t1 = std::tuple<int, double>;
    using t2 = std::tuple<double, int>;
    using t3 = std::tuple<int, double, char>;

    static_assert(type_set_eq<t1, t1>::value, "err");
    static_assert(type_set_eq<t1, t2>::value, "err");
    static_assert(!type_set_eq<t1, t3>::value, "err");
}

每个类型不允许在一个集合中出现多次。

5 个答案:

答案 0 :(得分:6)

Boost.Hana解决方案:

constexpr auto t1 = hana::tuple_t<int, double>;
constexpr auto t2 = hana::tuple_t<double, int>;
constexpr auto t3 = hana::tuple_t<int, double, char>;

auto same = [](auto a, auto b)
{
    auto to_occurrences_map = [](auto t)
    {
        return hana::fold(t, hana::make_map(), [](auto m, auto x)
        { 
            if constexpr(!hana::contains(decltype(m){}, x)) 
            { 
                return hana::insert(m, hana::make_pair(x, 1)); 
            }
            else { return ++(m[x]); }
        });
    };

    return to_occurrences_map(a) == to_occurrences_map(b);
};

static_assert(same(t1, t1));
static_assert(same(t1, t2));
static_assert(!same(t1, t3));

live wandbox example

答案 1 :(得分:6)

如果元组中的类型是唯一的,那么如果第一个元组中的所有类型都作为辅助结构的基础,则可以使用继承来回答。例如。 (C ++ 11方法):

#include <tuple>
#include <type_traits>

template <class T>
struct tag { };

template <class... Ts>
struct type_set_eq_helper: tag<Ts>... { };

template <class, class, class = void>
struct type_set_eq: std::false_type { };

template <bool...>
struct bool_pack { };

template <bool... Bs>
using my_and = std::is_same<bool_pack<Bs..., true>, bool_pack<true, Bs...>>;

template <class... Ts1, class... Ts2>
struct type_set_eq<std::tuple<Ts1...>, std::tuple<Ts2...>, typename std::enable_if< (sizeof...(Ts1) == sizeof...(Ts2)) && my_and< std::is_base_of<tag<Ts2>, type_set_eq_helper<Ts1...>>::value...  >::value  >::type  >:
   std::true_type { };

int main() {
    using t1 = std::tuple<int, double>;
    using t2 = std::tuple<double, int>;
    using t3 = std::tuple<int, double, char>;

    static_assert(type_set_eq<t1, t1>::value, "err");
    static_assert(type_set_eq<t1, t2>::value, "err");
    static_assert(!type_set_eq<t1, t3>::value, "err");
}

[Live demo]

答案 2 :(得分:3)

OP是否想要关注事件的数量(如主题建议 - “无序列表”,或者不是,如type_set_eq建议的那样,并不是很清楚。

所以,我将介绍两种变体。

从set开始 - 不重要的出现次数,则算法如下:

  1. 对于T1中的每种类型,检查它是否存在于T2
  2. 对于T2中的每种类型,检查它是否存在于T1
  3. 这两点都很重要 - 因为当只检查第1点时 - 我们有空T1列表的反例,它等于任何东西,当然,对于第2点来说,这是相同的反例(点是对称的)。

    要检查某些类型列表中是否存在某种类型 - 请使用此简单类模板:

    template <typename V, typename ...T> struct is_present;
    
    template <typename V> // <- type is not present in empty list
    struct is_present<V> : std::false_type {};
    
    template <typename V, typename F, typename ...T> 
    struct is_present<V,F,T...> : std::integral_constant<bool,
    // type is present in non-empty list
    // if it is first element
    std::is_same<V,F>::value 
    //  or it is present in the remaining list-but-first
    || is_present<V,T...>::value> {};
    

    由于我们处于C ++之前的17时代 - 那么fold expression尚不可用,所以下面的内容对于表示折叠而言是必要的:

    template <bool ...v> struct all_trues;
    template <> struct all_trues<> : std::true_type {};
    template <bool f, bool ...v> struct all_trues<f,v...> : 
        std::integral_constant<bool, 
                               f && all_trues<v...>::value> 
    {};
    

    然后定义比较两组类型的相等性如下:

    template <typename ...T1>
    struct are_set_of_types 
    {
        template <typename ...T2> 
        struct equal_to : all_trues<is_present<T1, T2...>::value...,  /*1*/
                                    is_present<T2, T1...>::value...>  /*2*/
       {};
    
    };
    

    当OP开始实施时,可以使用std::tuple完成此操作:

    template <typename T1, typename T2>
    struct type_set_eq;
    template <typename ...T1, typename ...T2>
    struct type_set_eq<std::tuple<T1...>, std::tuple<T2...>> 
          : are_set_of_types <T1...>::template equal_to<T2...> 
    {};
    

    当出现次数很重要时,算法如下:

    1. 检查两个序列的大小是否相等
    2. 检查左侧序列中每个值的出现次数是否等于第二次序列中该值的出现次数
    3. 这两点应保证序列相等而不考虑元素的顺序。

      因此,与集合比较的区别在于此类模板:

      template <typename V, typename ...T>
      struct count_occurences;
      
      template <typename V> 
      // number of occurrences in empty list is 0
      struct count_occurences<V> : std::integral_constant<std::size_t, 0u> {};
      
      template <typename V, typename F, typename ...T> 
      // number of occurrences in non-empty list is 
      struct count_occurences<V,F,T...> : std::integral_constant<std::size_t, 
      // 1 if type is same as first type (or 0 otherwise)
      (std::is_same<V,F>::value ? 1u : 0u) 
      // plus number of occurrences in remaining list
      + count_occurences<V,T...>::value> {};
      

      用于检查两个序列相等的模板,无需考虑订单:

      template <typename ...T1>
      struct are_unordered_types_sequences 
      {
          // when number of occurrences is important
          template <typename ...T2> 
          struct equal_to : all_trues<
                  /*1*/ sizeof...(T1) == sizeof...(T2), 
                  /*2*/ (count_occurences<T1, T1...>::value == count_occurences<T1, T2...>::value)...>
         {};
      };
      

      和元组变体:

      template <typename T1, typename T2>
      struct type_set_eq;
      template <typename ...T1, typename ...T2>
      struct type_set_eq<std::tuple<T1...>, std::tuple<T2...>> 
            : are_unordered_types_sequences<T1...>::template equal_to<T2...> 
      {};
      

      Wandbox example I used to play with these templates

答案 3 :(得分:2)

当然不是最好的解决方案,但我们可以一次只使用一种类型,看看它是否在另一个列表中。如果我们找不到它们,它们就不平等了。如果我们这样做,请重复两个较小的列表:

template <class A, class B>
struct type_set_eq : std::false_type { };

// base case: two empty packs are equal
template <>
struct type_set_eq<std::tuple<>, std::tuple<>> : std::true_type { };

template <class Lhs, class Done, class Rhs>
struct type_set_eq_impl;

// at least one element in each - we use the middle type to keep track 
// of all the types we've gone through from the Ys
template <class X, class... Xs, class... Ds, class Y, class... Ys>
struct type_set_eq_impl<std::tuple<X, Xs...>, std::tuple<Ds...>, std::tuple<Y, Ys...>>
    : std::conditional_t<
        std::is_same<X,Y>::value,
        type_set_eq<std::tuple<Xs...>, std::tuple<Ds..., Ys...>>,
        type_set_eq_impl<std::tuple<X, Xs...>, std::tuple<Ds..., Y>, std::tuple<Ys...>>>
{ };

// if we run out, we know it's false
template <class X, class... Xs, class... Ds>
struct type_set_eq_impl<std::tuple<X, Xs...>, std::tuple<Ds...>, std::tuple<>>
    : std::false_type
{ };

template <class... Xs, class... Ys>
struct type_set_eq<std::tuple<Xs...>, std::tuple<Ys...>>
    : std::conditional_t<
        (sizeof...(Xs) == sizeof...(Ys)),
        type_set_eq_impl<std::tuple<Xs...>, std::tuple<>, std::tuple<Ys...>>,
        std::false_type>
{ };

// shortcut to true
template <class... Xs>
struct type_set_eq<std::tuple<Xs...>, std::tuple<Xs...>>
    : std::true_type 
{ };

答案 4 :(得分:0)

在 C++17 中,我们可以使用折叠表达式相当简单地解决这个问题:

template <typename T, typename... Rest>
constexpr bool is_one_of_v = (std::is_same_v<T, Rest> || ...);

// Given:
// typename... Types1, typename... Types2
constexpr bool is_same_set_v = 
    // |{Types1...}| == |{Types2...}| and {Types1...} subset of {Types2...}:
    sizeof...(Types1) == sizeof...(Types2)
    && (is_one_of_v<Types1, Types2...> && ...);

// Alternative if you want to allow repeated set elements; more mathematical:
constexpr bool is_same_set_v = 
    // {Types1...} subset of {Types2...} and vice versa.
    (is_one_of_v<Types1, Types2...> && ...)
    && (is_one_of_v<Types2, Types1...> && ...);

向后移植到 C++14 很简单:

template <bool...> struct bools {};

template <bool... Vs>
constexpr bool all_of_v = std::is_same<bools<true, Vs...>, bools<Vs..., true>>::value;

template <bool... Vs>
constexpr bool any_of_v = !all_of_v<!Vs...>;

template <typename T, typename... Rest>
constexpr bool is_one_of_v = any_of_v<std::is_same<T, Rest>::value...>;

// Given:
// typename... Types1, typename... Types2
constexpr bool is_same_set_v = 
    // |{Types1...}| == |{Types2...}| and {Types1...} subset of {Types2...}:
    sizeof...(Types1) == sizeof...(Types2)
    && all_of_v<is_one_of_v<Types1, Types2...>...>;

// Alternative if you want to allow repeated set elements; more mathematical:
constexpr bool is_same_set_v = 
    // {Types1...} subset of {Types2...} and vice versa.
    all_of_v<is_one_of_v<Types1, Types2...>...>
    && all_of_v<is_one_of_v<Types2, Types1...>...>;

可以通过更改这些模板变量以通过结构体来降级到 C++11,如下所示:

template <bool... Vs>
struct all_of {
    static constexpr bool value =
        std::is_same<bools<true, Vs...>, bools<Vs..., true>>::value;
};