21

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.

#include <tuple>
#include <type_traits>

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

template <typename ... Types1, typename ... Types2>
struct is_subset_of<std::tuple<Types1...>, std::tuple<Types2...>>
    : std::true_type
{
    // Should only be true_type if Types1 is a subset of Types2
};

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");
}

Every type is not allowed to occur more than once in a set.

It would be nice if the solution works with C++11.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Tobias Hermann
  • 9,936
  • 6
  • 61
  • 134
  • I had a [similar question](http://stackoverflow.com/questions/42488479/compare-two-sets-of-types-for-equality) before, but right now I am unable to modify my code from equality check to subset check. – Tobias Hermann Mar 03 '17 at 14:07

9 Answers9

24
#include <tuple>
#include <type_traits>

template <typename T, typename... Ts>
constexpr bool contains = (std::is_same<T, Ts>{} || ...);

template <typename Subset, typename Set>
constexpr bool is_subset_of = false;

template <typename... Ts, typename... Us>
constexpr bool is_subset_of<std::tuple<Ts...>, std::tuple<Us...>>
           = (contains<Ts, Us...> && ...);

DEMO

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
  • 2
    Might be informative to add that this only works from C++17 onwards. But this is by far the most elegant solution! – Maarten Bamelis Mar 03 '17 at 15:02
  • 2
    You can write `contains` without recursion; `contains = std::is_same::value || ...;` Which, in turn, would let you write `is_subset_of` without `contains`. I don't think I can write "is an ascending sequence of subsets" without recursion or abstraction however. – Yakk - Adam Nevraumont Mar 03 '17 at 15:56
  • @PiotrSkotnicki I was wrong about being able to do it in one expression; the inability for `...` to pick which packs to expand makes that not possible as far as I can tell (maybe by a cross-product like metafunction or somesuch?) – Yakk - Adam Nevraumont Mar 03 '17 at 18:53
8

If you can use C++17 features, I highly recommend using Piotr Skotnicki's solution!

I had to implement this functionality a while ago. I am just going to copy-paste the code I came up with at that point.

I am not claiming that this is the best or most elegant way to implement this kind of check! I did not bother thinking about the edge cases too much; you may need to adapt the code to fit your requirements.

To clarify: ContainsTypes<Lhs, Rhs> checks if Rhs is a subset of Lhs.


  template <typename Tuple, typename T>
  struct ContainsType;

  template <typename T, typename U, typename... Ts>
  struct ContainsType<std::tuple<T, Ts...>, U>
  {
      static const bool VALUE = ContainsType<std::tuple<Ts...>, U>::VALUE;
  };

  template <typename T, typename... Ts>
  struct ContainsType<std::tuple<T, Ts...>, T>
  {
      static const bool VALUE = true;
  };

  template <typename T>
  struct ContainsType<std::tuple<>, T>
  {
      static const bool VALUE = false;
  };

  // -----

  template <typename Lhs, typename Rhs>
  struct ContainsTypes;

  template <typename Tuple, typename T, typename... Ts>
  struct ContainsTypes<Tuple, std::tuple<T, Ts...>>
  {
      static const bool VALUE = ContainsType<Tuple, T>::VALUE && ContainsTypes<Tuple, std::tuple<Ts...>>::VALUE;
  };

  template <typename Tuple>
  struct ContainsTypes<Tuple, std::tuple<>>
  {
      static const bool VALUE = true;
  };
Community
  • 1
  • 1
Maarten Bamelis
  • 2,243
  • 19
  • 32
  • This is exactly what I needed. Thanks. I [modified it a bit](http://coliru.stacked-crooked.com/a/90177820413ec96f) but only to better fit into what I already have. – Tobias Hermann Mar 03 '17 at 15:30
  • @TobiasHermann Excuse my lazy copy-pasting of code. I am glad to hear that you got it to work with your code. Also be sure to look at Piotr Skotnicki's answer as I find it the most elegant solution given you have access to C++17 features. – Maarten Bamelis Mar 03 '17 at 15:40
  • 1
    Only work if no type appears more than once in the list. – Oliv Mar 04 '17 at 10:23
  • @Oliv Good catch! I assumed a _set_ to not contain duplicates (a set is a collection of distinct objects). However, that might not fit your needs, indeed. I also wrote some struct to remove duplicate types from a `std::tuple` which I applied to both arguments before checking them. – Maarten Bamelis Mar 06 '17 at 08:49
  • @MaartenBamelis You right, this is all about interpretation of what are the elements of a parameter pack? considering that two templates are not necessarily equals when you permute the type of the template argument list, I would say that from a template perspective, a parameter pack is a map from a categorie associating element_position to type, where element_position is an ordered set. So, when I read a subset of a parameter pack, while a parameter pack is not necessarily a set, I suppose the question admit many contradictory answers. – Oliv Mar 06 '17 at 10:43
4

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.

Community
  • 1
  • 1
AndyG
  • 39,700
  • 8
  • 109
  • 143
3

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.

skypjack
  • 49,335
  • 19
  • 95
  • 187
3

Not exactly what you asked but... just for fun, using std::is_base_of you can create (in C++14, at least) a constexpr function that works like your struct.

The following is a working example (only C++14)

#include <tuple>
#include <iostream>
#include <type_traits>

template <typename ... Ts>
struct foo : std::tuple<Ts>...
 { };

template <typename ... Ts1, typename ... Ts2>
bool isSubsetOf (std::tuple<Ts1...> const &, std::tuple<Ts2...> const &)
 {
   bool ret { true };

   using un = int[];
   using d2 = foo<Ts2...>;

   (void)un { (ret &= std::is_base_of<std::tuple<Ts1>, d2>::value, 0)... };

   return ret;
 }


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

    std::cout << isSubsetOf(t1{}, t1{}) << std::endl;  // print 1
    std::cout << isSubsetOf(t1{}, t2{}) << std::endl;  // print 1
    std::cout << isSubsetOf(t2{}, t1{}) << std::endl;  // print 1
    std::cout << isSubsetOf(t1{}, t3{}) << std::endl;  // print 1
    std::cout << isSubsetOf(t3{}, t1{}) << std::endl;  // print 0
 }
max66
  • 65,235
  • 10
  • 71
  • 111
3
constexpr bool any_of() { return false; }
template<class...Bools>
constexpr bool any_of( bool b, Bools... bools ) {
  return b || any_of(bools...);
}
constexpr bool all_of() { return true; }
template<class...Bools>
constexpr bool all_of( bool b, Bools...bools ) {
  return b && all_of(bools...);
}
template<class T0, class...Ts>
struct contains_t : std::integral_constant<bool,
  any_of( std::is_same<T0, Ts>::value... )
> {};

template<class Tuple0, class Tuple1>
struct tuple_subset_of;

template<class...T0s, class...T1s>
struct tuple_subset_of< std::tuple<T0s...>, std::tuple<T1s...> >:
  std::integral_constant<bool,
    all_of( contains_t<T0s, T1s...>::value... )
  >
{};

Live example.

This is designed to permit easy improvement post C++17 -- replace any_of and all_of recursive implementations with fold expressions.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
2

I suppose I'll throw my hat in the ring. This is a C++11 solution like the OP asked for, I realize that C++17 has much nicer features. It's a type-only solution (no explicit static const bool or the like, only true_type and false_type, which have their own internal bool)

The downside is that this solution forced me to implement logical_or and logical_and, which we'd get for free in C++17 in the form of conjunction and disjunction).

Miraculously the code is a tad shorter than Maartan Barnelis' solution, though arguably less readable

namespace detail
{
template<class T, class U>
struct logical_or : std::true_type{};

template<>
struct logical_or<std::false_type, std::false_type> : std::false_type{};

template<class...>
struct logical_and : std::false_type{};

template<>
struct logical_and<std::true_type, std::true_type> : std::true_type{};
}

template<class...>
struct contains : std::false_type{};

template<class T>
struct contains<T, T> : std::true_type{};

template<class Type, class Types2Head, class... Types2>
struct contains<Type, Types2Head, Types2...> : detail::logical_or<typename std::is_same<Type, Types2Head>::type, typename contains<Type, Types2...>::type>{};

template<class...>
struct is_subset_of : std::false_type{};

template<class Type1, class... Types2>
struct is_subset_of<std::tuple<Type1>, std::tuple<Types2...>> : contains<Type1, Types2...>{};

template<class Type1Head, class... Types1, class... Types2>
struct is_subset_of<std::tuple<Type1Head, Types1...>, std::tuple<Types2...>> : detail::logical_and<typename contains<Type1Head, Types2...>::type, typename is_subset_of<std::tuple<Types1...>, std::tuple<Types2...>>::type>{};

Demo

AndyG
  • 39,700
  • 8
  • 109
  • 143
2

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
{ };
max66
  • 65,235
  • 10
  • 71
  • 111
2

is_subset_of version of an answer from your previous question:

#include <tuple>
#include <type_traits>

template <class T>
struct tag { };

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

template <class, class, class = void>
struct is_subset_of: 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 is_subset_of<std::tuple<Ts1...>, std::tuple<Ts2...>, typename std::enable_if< my_and< std::is_base_of<tag<Ts1>, is_subset_of_helper<Ts2...>>::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(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");
}

[live demo]

W.F.
  • 13,888
  • 2
  • 34
  • 81