Check if one set of types is a subset of the other
Asked Answered
P

9

22

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.

Parch answered 3/3, 2017 at 14:6 Comment(1)
I had a similar question before, but right now I am unable to modify my code from equality check to subset check.Parch
O
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;
  };
Oilskin answered 3/3, 2017 at 14:19 Comment(5)
This is exactly what I needed. Thanks. I modified it a bit but only to better fit into what I already have.Parch
@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.Oilskin
Only work if no type appears more than once in the list.Enhanced
@Enhanced 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.Oilskin
@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.Enhanced
A
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

Autotransformer answered 3/3, 2017 at 14:37 Comment(3)
Might be informative to add that this only works from C++17 onwards. But this is by far the most elegant solution!Oilskin
You can write contains without recursion; contains = std::is_same<T, Ts>::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.Goodtempered
@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?)Goodtempered
O
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;
  };
Oilskin answered 3/3, 2017 at 14:19 Comment(5)
This is exactly what I needed. Thanks. I modified it a bit but only to better fit into what I already have.Parch
@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.Oilskin
Only work if no type appears more than once in the list.Enhanced
@Enhanced 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.Oilskin
@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.Enhanced
W
5

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.

Winze answered 3/3, 2017 at 16:22 Comment(0)
A
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.

Announcement answered 3/3, 2017 at 14:36 Comment(0)
G
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
 }
Gyrostatics answered 3/3, 2017 at 15:0 Comment(0)
G
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.

Goodtempered answered 3/3, 2017 at 18:52 Comment(0)
W
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

Winze answered 3/3, 2017 at 16:6 Comment(0)
G
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
{ };
Gyrostatics answered 3/3, 2017 at 16:51 Comment(2)
You are welcome, but it's skypjack, not Skypjack!! :-)Announcement
@Announcement - I have the bad habit to start the other's names with capital letters. Sorry :-(Gyrostatics
A
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]

Avatar answered 6/3, 2017 at 9:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.