Check if one set of types is a subset of the other

前端 未结 9 822
甜味超标
甜味超标 2021-01-01 17:54

How can one check if one parameter pack (interpreted as a set) is a subset of another one?

So far I only have the frame (using std::tuple), but no functionality.

相关标签:
9条回答
  • 2021-01-01 18:38

    You can do that with a class like the following one:

    template<typename... Set>
    struct Check {
        template<typename Type>
        static constexpr bool verify() {
            using accumulator_type = bool[];
            bool check = false;
            accumulator_type accumulator = { (check = check || std::is_same<Type, Set>())... };
            (void)accumulator;
            return check;
        }
    
        template<typename... SubSet>
        static constexpr bool contain() {
            using accumulator_type = bool[];
            bool check = true;
            accumulator_type accumulator = { (check = check && verify<SubSet>())... };
            (void)accumulator;
            return check;
        }
    };
    

    Turning it in an example based on function is straightforward.
    It follows a possible implementation adapted to your code:

    #include <tuple>
    #include <type_traits>
    
    template<typename... Set>
    struct Check {
        template<typename Type>
        static constexpr bool verify() {
            using accumulator_type = bool[];
            bool check = false;
            accumulator_type accumulator = { (check = check || std::is_same<Type, Set>())... };
            (void)accumulator;
            return check;
        }
    
        template<typename... SubSet>
        static constexpr bool contain() {
            using accumulator_type = bool[];
            bool check = true;
            accumulator_type accumulator = { (check = check && verify<SubSet>())... };
            (void)accumulator;
            return check;
        }
    };
    
    
    template <typename, typename>
    struct is_subset_of;
    
    template <typename ... Types1, typename ... Types2>
    struct is_subset_of<std::tuple<Types1...>, std::tuple<Types2...>> {
        static constexpr bool value = Check<Types2...>::template contain<Types1...>();
    };
    
    int main() {
        using t1 = std::tuple<int, double>;
        using t2 = std::tuple<double, int>;
        using t3 = std::tuple<int, double, char>;
    
        static_assert(is_subset_of<t1, t1>::value, "err");
        static_assert(is_subset_of<t1, t2>::value, "err");
        static_assert(is_subset_of<t2, t1>::value, "err");
        static_assert(is_subset_of<t2, t3>::value, "err");
        static_assert(!is_subset_of<t3, t2>::value, "err");
    }
    

    The work is done within the class Check and it's methods contain and verify.
    The contain member function is the entry point. It uses a common trick (while waiting for the fold expressions) to unpack the subset and requires an explicit check for each contained type. The member function verify does the rest, by matching the single type with the given set.

    Let me know if I can give you more details or it's clear enough as it stands.


    See it running on coliru.

    0 讨论(0)
  • 2021-01-01 18:41

    A (little more) serious answer (than the previous one): Using a trick that Skypjack showed (thanks!), you can avoid recursion for both ContainsType and ContainsTypes.

    The following is a working example (that works not only with std::tuples, but with generic (also different) type containers).

    #include <tuple>
    #include <type_traits>
    
    template <typename T, typename ... Ts>
    struct cType
    {
        static const bool value {
            ! std::is_same<std::integer_sequence<bool,
                              false, std::is_same<T, Ts>::value...>,
                           std::integer_sequence<bool,
                              std::is_same<T, Ts>::value..., false>>::value };
     };
    
    template <typename, typename>
    struct isSubsetOf : std::false_type
     { };
    
    template <template <typename...> class C1, template <typename...> class C2,
              typename ... Ts1, typename ... Ts2>
    struct isSubsetOf<C1<Ts1...>, C2<Ts2...>>
        : std::integral_constant<bool,
             std::is_same<std::integer_sequence<bool,
                             true, cType<Ts1, Ts2...>::value...>,
                          std::integer_sequence<bool,
                             cType<Ts1, Ts2...>::value..., true>
                       >::value>
     { };
    
    
    int main()
    {
        using t1 = std::tuple<int, double>;
        using t2 = std::tuple<double, int>;
        using t3 = std::tuple<int, double, char>;
    
        static_assert(isSubsetOf<t1, t1>::value, "err");
        static_assert(isSubsetOf<t1, t2>::value, "err");
        static_assert(isSubsetOf<t2, t1>::value, "err");
        static_assert(isSubsetOf<t2, t3>::value, "err");
        static_assert(!isSubsetOf<t3, t2>::value, "err");
    }
    

    This example uses std::integer_sequence that is a C++14 feature, but it is trivial to create a C++11 substitute, like:

    template <typename T, T ... ts>
    struct integerSequence
    { };
    
    0 讨论(0)
  • 2021-01-01 18:42

    Here's a C++17 answer that I believe is quite simpler than Piotr's answer:

    template <class T, class... U>
    struct contains : std::disjunction<std::is_same<T, U>...>{};
    
    template <typename...>
    struct is_subset_of : std::false_type{};
    
    template <typename... Types1, typename ... Types2>
    struct is_subset_of<std::tuple<Types1...>, std::tuple<Types2...>> : std::conjunction<contains<Types1, Types2...>...> {};
    

    Demo

    disjunction and conjunction are new type traits introduced in C++17. We can take advantage of these to check if at least one type in the second tuple matches "the next type" in the first tuple, which we use parameter pack expansion extensively for.

    0 讨论(0)
提交回复
热议问题