简体   繁体   中英

Compare two multisets of types for equality

As this question doesn't seem to cover all useful cases I decided to fill the gap with this little question of mine. Is there a way to answer if two multisets of types are equal?

#include <tuple>
#include <type_traits>

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

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

int main() {

    static_assert(type_multiset_eq<std::tuple<char, int, double, float, int, float>, std::tuple<float, char, int, double, int, float>>::value, "err");
    static_assert(!type_multiset_eq<std::tuple<char, int, double, float, int, float>, std::tuple<char, int, double, int, float>>::value, "err");
    static_assert(type_multiset_eq<std::tuple<char, char, char, float, float, float>, std::tuple<char, float, char, float, char, float>>::value, "err");
    static_assert(!type_multiset_eq<std::tuple<int, int>, std::tuple<int, int, int>>::value, "err");
}

In the answer I focused a bit on efficiency. The method can be divided on four basic steps:

  1. Rank types in each pack
  2. Sort types in packs in base of given rank
  3. Create two sets of unique elements (also types) containing information about the types and its frequencies from the previous version of packs (inheriting from these types)
  4. Investigate if the other multiset of types is deriving from the same types (with frequencies)

The approach should be O(N log N) depending on the number of type


C++14 approach:

#include <utility>
#include <array>

template <class T>
constexpr T ilog2(T n) {
    return n == static_cast<T>(1) ? static_cast<T>(0) : ilog2(n >> static_cast<T>(1)) + 1;
}

template <class T>
constexpr T ipow2(T n) {
    return static_cast<T>(1) << n;
}

template <std::size_t N>
struct s_exp {
    static constexpr std::size_t exp = ipow2(ilog2(N-1)+1);
};

template <std::size_t I, class T>
struct itag { };

template <std::size_t D, std::size_t I, class T>
struct vvtag { };

template <std::size_t S, std::size_t I, class T>
struct vtag: virtual vvtag<I/S, (I%S) / ((s_exp<S>::exp + (2 << (I/S)) - 1)/(2 << (I/S))), T> { };

template <class... Ts>
struct pack {
   static constexpr std::size_t size = sizeof...(Ts);
};

template <class P, class = std::make_index_sequence<P::size>>
struct ipack;

template <class... Ts, std::size_t... Is>
struct ipack<pack<Ts...>, std::index_sequence<Is...>>: itag<Is, Ts>... { 
    static constexpr std::size_t size = sizeof...(Ts);
};

template <std::size_t I, class T>
T ipack_element(itag<I, T>);

template <class IP, class = std::make_index_sequence<IP::size * (ilog2(IP::size - 1) + 1) >>
struct vpack;

template <class IP, std::size_t... Is>
struct vpack<IP, std::index_sequence<Is...>>: vtag<IP::size, Is, decltype(ipack_element<Is % IP::size>(IP{}))>... { 
    static constexpr std::size_t size = IP::size;
};

template <class A, class CompArr>
constexpr int partition(A &a, int lo, int hi, const CompArr &ca) {
    int x = a[lo];
    int i = lo, j = hi; 
    while (true) { 
        while (ca[a[j]] > ca[x])
            j--;
        while (ca[a[i]] < ca[x])
            i++;
        if (i < j) {
            auto w = a[i];
            a[i] = a[j];
            a[j] = w;
            i++;
            j--;
        } else 
            return j;
    }
}

template <class A, class CompArr>
constexpr void quicksort(A &a, int lo, int hi, const CompArr &ca) {
    if (lo < hi) {  
        auto q = partition(a, lo, hi, ca); 
        quicksort(a, lo, q, ca); 
        quicksort(a, q+1, hi, ca);
    }
}

template <class... Ts, std::size_t... Is>
constexpr std::array<std::size_t, sizeof...(Ts)> rank(itag<0, ipack<pack<Ts...>, std::index_sequence<Is...>>>) {
    return {{!std::is_base_of<vvtag<0, 0, decltype(ipack_element<Is>(ipack<pack<Ts...>>{}))>, vpack<ipack<pack<Ts...>>>>::value...}};
}

template <std::size_t N, class... Ts, std::size_t... Is>
constexpr std::array<std::size_t, sizeof...(Ts)> rank(itag<N, ipack<pack<Ts...>, std::index_sequence<Is...>>>) {
    constexpr auto prev = rank(itag<N - 1, ipack<pack<Ts...>>>{});
    return {{prev[Is]*2 + !std::is_base_of<vvtag<N, prev[Is]*2, decltype(ipack_element<Is>(ipack<pack<Ts...>>{}))>, vpack<ipack<pack<Ts...>>>>::value...}};
}

template <class... Ts, std::size_t... Is>
constexpr std::array<std::size_t, sizeof...(Ts)> sort_types_impl(ipack<pack<Ts...>, std::index_sequence<Is...>>) {
   constexpr std::size_t TS = sizeof...(Ts);
   auto compare_enabler = rank(itag<ilog2(TS - 1), ipack<pack<Ts...>, std::index_sequence<Is...>>>{});
   std::size_t result[TS] { Is... };
   quicksort(result, 0, sizeof...(Is) - 1, compare_enabler);
   return {{ result[Is]... }};
}

template <class>
struct sort_types;

template <class... Ts>
struct sort_types<pack<Ts...>>: sort_types<ipack<pack<Ts...>>> { };

template <class... Ts, std::size_t... Is>
struct sort_types<ipack<pack<Ts...>, std::index_sequence<Is...>>> {
    static constexpr auto idxs = sort_types_impl(ipack<pack<Ts...>>{});
    using type = pack<decltype(ipack_element<idxs[Is]>(ipack<pack<Ts...>>{}))...>;
};

struct dummy { };

template <class... Ts>
struct unique_pack: Ts... { 
    static constexpr std::size_t size = sizeof...(Ts);

    template <class Up>
    constexpr bool operator==(Up) {
        bool result = size == Up::size;
        bool ibo[sizeof...(Ts)] = { std::is_base_of<Ts, Up>::value... };
        for (std::size_t i = 0; i < sizeof...(Ts); i++)
            result &= ibo[i];
        return  result;
    }
};

template <class>
struct multiset;

template <class... Ts>
struct multiset<pack<Ts...>>: multiset<ipack<pack<Ts...>>> {};

template <class... Ts, std::size_t... Is>
struct multiset<ipack<pack<Ts...>, std::index_sequence<Is...>>> {
   using sorted_pack = typename sort_types<pack<Ts..., dummy>>::type;
   static constexpr std::array<bool, sizeof...(Ts)> const unique_types() {
       return {{ !std::is_same< decltype(ipack_element<Is>(ipack<sorted_pack>{})), decltype(ipack_element<Is + 1>(ipack<sorted_pack>{})) >::value... }};
   }
   static constexpr std::size_t unique_count() {
       constexpr std::array<bool, sizeof...(Ts)> const ut = unique_types();
       std::size_t result = 0;
       for (std::size_t i = 0; i < sizeof...(Ts); i++)
           result += ut[i];
       return result;
   }

   template <std::size_t... Is2>
   static constexpr std::array<std::size_t, unique_count()> const unique_idxs(std::index_sequence<Is2...>) {
       std::size_t result[unique_count()] {};
       std::size_t cur = 0;
       constexpr std::array<bool, sizeof...(Ts)> const ut = unique_types();
       for (std::size_t i = 0; i < sizeof...(Ts); i++) {
           if (ut[i])
               result[cur++] = i;
       }
       return {{ result[Is2]... }};
   }

   template <std::size_t... Is2>
   static constexpr std::array<std::size_t, unique_count()> const unique_counts(std::index_sequence<Is2...>) {
       std::size_t result[unique_count()] {};
       std::size_t cur = 0;
       constexpr auto ut = unique_types();
       for (std::size_t i = 0; i < sizeof...(Ts); i++) {
           if (ut[i])
               result[cur++]++;
           else
               result[cur]++;
       }
       return {{ result[Is2]... }};
   }

   template <std::size_t... Is2>
   static auto make_type(std::index_sequence<Is2...>) {
       constexpr std::array<std::size_t, unique_count()> const idxs = unique_idxs(std::index_sequence<Is2...>{});
       constexpr std::array<std::size_t, unique_count()> const counts = unique_counts(std::index_sequence<Is2...>{});
       return unique_pack<itag<counts[Is2], decltype(ipack_element<idxs[Is2]>(ipack<sorted_pack>{}))>...>{};
   }

   template <class T = multiset, std::size_t UC = T::unique_count()>
   using type = decltype(make_type(std::make_index_sequence<UC>{}));
};

template <class P1, class P2>
constexpr bool multiset_equality(P1, P2) {
    return typename multiset<P1>::template type<>{} == typename multiset<P2>::template type<>{} && typename multiset<P2>::template type<>{} == typename multiset<P1>::template type<>{};
}

int main() {
    static_assert(multiset_equality(pack<char, int, double, float, int, float>{}, pack<float, char, int, double, int, float>{}),"!");
    static_assert(!multiset_equality(pack<char, int, double, float, int, float>{}, pack<char, int, double, int, float>{}),"!");
    static_assert(multiset_equality(pack<char, char, char, float, float, float>{}, pack<char, float, char, float, char, float>{}),"!");
    static_assert(!multiset_equality(pack<int, int>{}, pack<int, int, int>{}),"!");
}

[live demo]

You can use the following: it removes from both side type until first is empty, or second doesn't match:

template <typename T, typename Tuple, typename Res = std::tuple<>>
struct remove_type_from_tuple;

template <typename T, typename ... Ts, typename ...Res>
struct remove_type_from_tuple<T, std::tuple<T, Ts...>, std::tuple<Res...>>
{
    using type = std::tuple<Res..., Ts...>;
};

template <typename T, typename T2, typename ... Ts, typename ...Res>
struct remove_type_from_tuple<T, std::tuple<T2, Ts...>, std::tuple<Res...>>
{
    using type = typename remove_type_from_tuple<T,
                                                 std::tuple<Ts...>,
                                                 std::tuple<Res..., T2>>::type;
};

template <typename T, typename Res>
struct remove_type_from_tuple<T, std::tuple<>, Res>
{
    using type = void;
};

template <typename T, typename Res>
struct remove_type_from_tuple<T, void, Res>
{
    using type = void;
};

template <typename Tuple1, typename Tuple2>
struct diff_types_from_tuple;

template <typename T, typename ...Ts, typename Tuple>
struct diff_types_from_tuple<std::tuple<T, Ts...>, Tuple>
{
    using type =
        typename diff_types_from_tuple<std::tuple<Ts...>,
                                       typename remove_type_from_tuple<T, Tuple>::type
                                       >::type;
};

template <typename Tuple>
struct diff_types_from_tuple<std::tuple<>, Tuple>
{
    using type = Tuple;
};


template <typename Tuple1, typename Tuple2>
struct type_multiset_eq :
    std::is_same<std::tuple<>,
                 typename diff_types_from_tuple<Tuple1, Tuple2>::type>
{
};

Demo

Interesting question...

Taking inspiration from your answer (well... copying it) in the original question (the type_set_eq one), adding a type counter ( countT ) and removing the helper struct and the tag struct, I suppose that you can simply write something as follows

#include <tuple>
#include <type_traits>

template <typename ...>
struct countT;

template <typename T>
struct countT<T>
 { static constexpr std::size_t value { 0U }; };

template <typename T, typename T0, typename ... Ts>
struct countT<T, T0, Ts...>
 { static constexpr std::size_t value { countT<T, Ts...>::value }; };

template <typename T, typename ... Ts>
struct countT<T, T, Ts...>
 { static constexpr std::size_t value { 1U + countT<T, Ts...>::value }; };

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

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

template <typename, typename, typename = void>
struct type_multiset_eq : std::false_type
 { };

template <template <typename ...> class C1, typename ... Ts1,
          template <typename ...> class C2, typename ... Ts2>
struct type_multiset_eq<C1<Ts1...>, C2<Ts2...>,
   typename std::enable_if<
         (sizeof...(Ts1) == sizeof...(Ts2))
      && (my_and<(    countT<Ts1, Ts1...>::value
                   == countT<Ts1, Ts2...>::value)...>::value)
      >::type>
 : std::true_type
 { };

int main()
 {
   static_assert( type_multiset_eq<
      std::tuple<char, int, double, float, int, float>,
      std::tuple<float, char, int, double, int, float>>::value, "err");
   static_assert( ! type_multiset_eq<
      std::tuple<char, int, double, float, int, float>,
      std::tuple<char, int, double, int, float>>::value, "err");
   static_assert( type_multiset_eq<
      std::tuple<char, char, char, float, float, float>,
      std::tuple<char, float, char, float, char, float>>::value, "err");
   static_assert( ! type_multiset_eq<
      std::tuple<int, int>,
      std::tuple<int, int, int>>::value, "err");
 }

In case you can use C++14, you can substitute the countT type trait with the following constexpr function

template <typename T, typename ... Ts>
constexpr std::size_t cntT ()
 {
   using unused = std::size_t[];

   std::size_t  ret { 0U };

   (void)unused { 0U, ret += (std::is_same<T, Ts>::value ? 1U : 0U)... };

   return ret;
 }

Just for fun, I propose another solution based on type counting (like the first one) with the same complexity (O(n^2), I suppose) but a little smarter (end the check at the first difference)

#include <tuple>
#include <type_traits>

template <typename ...>
struct countT;

template <typename T>
struct countT<T>
 { static constexpr std::size_t value { 0U }; };

template <typename T, typename T0, typename ... Ts>
struct countT<T, T0, Ts...>
 { static constexpr std::size_t value { countT<T, Ts...>::value }; };

template <typename T, typename ... Ts>
struct countT<T, T, Ts...>
 { static constexpr std::size_t value { 1U + countT<T, Ts...>::value }; };

template <typename, typename, typename>
struct eqCountT;

template <template <typename ...> class C, typename T, typename ... Ts1,
          typename ... Ts2, typename ... Ts3>
struct eqCountT<C<T, Ts1...>, C<Ts2...>, C<Ts3...>>
    : std::integral_constant<bool,
         (countT<T, Ts2...>::value == countT<T, Ts3...>::value)>
 { };

template <template <typename ...> class C, typename T2, typename T3>
struct eqCountT<C<>, T2, T3> : std::true_type
 { };

template <typename T1, typename T2, typename T3,
          bool = eqCountT<T1, T2, T3>::value>
struct mseqH;

template <template <typename ...> class C, typename T2, typename T3>
struct mseqH<C<>, T2, T3, true> : std::true_type
 { };

template <typename T1, typename T2, typename T3>
struct mseqH<T1, T2, T3, false> : std::false_type
 { };

template <template <typename ...> class C, typename T, typename ... Ts1,
          typename T2, typename T3>
struct mseqH<C<T, Ts1...>, T2, T3, true> : mseqH<C<Ts1...>, T2, T3>
 { };

template <typename, typename>
struct type_multiset_eq;

template <template <typename ...> class C1, typename ... Ts1,
          template <typename ...> class C2, typename ... Ts2>
struct type_multiset_eq<C1<Ts1...>, C2<Ts2...>>
    : std::integral_constant<bool,
            (sizeof...(Ts1) == sizeof...(Ts2))
         && mseqH<C1<Ts1...>, C1<Ts1...>, C1<Ts2...>>::value>
 { };

int main()
 {
   static_assert( type_multiset_eq<
      std::tuple<char, int, double, float, int, float>,
      std::tuple<float, char, int, double, int, float>>::value, "err");
   static_assert( ! type_multiset_eq<
      std::tuple<char, int, double, float, int, float>,
      std::tuple<char, int, double, int, float>>::value, "err");
   static_assert( type_multiset_eq<
      std::tuple<char, char, char, float, float, float>,
      std::tuple<char, float, char, float, char, float>>::value, "err");
   static_assert( ! type_multiset_eq<
      std::tuple<int, int>,
      std::tuple<int, int, int>>::value, "err");
 }

In case you can use C++14, you can substitute the countT type trait with the following constexpr function

template <typename T, typename ... Ts>
constexpr std::size_t cntT ()
 {
   using unused = std::size_t[];

   std::size_t  ret { 0U };

   (void)unused { 0U, ret += (std::is_same<T, Ts>::value ? 1U : 0U)... };

   return ret;
 }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM