Reputation: 10946
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.
Upvotes: 22
Views: 4570
Reputation: 13988
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");
}
Upvotes: 2
Reputation: 275730
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... )
>
{};
This is designed to permit easy improvement post C++17 -- replace any_of
and all_of
recursive implementations with fold expressions.
Upvotes: 3
Reputation: 66230
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::tuple
s, 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
{ };
Upvotes: 2
Reputation: 2423
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;
};
Upvotes: 8
Reputation: 48487
#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...> && ...);
Upvotes: 24
Reputation: 41110
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...>...> {};
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.
Upvotes: 5
Reputation: 41110
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>{};
Upvotes: 2
Reputation: 66230
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
}
Upvotes: 3
Reputation: 50550
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.
Upvotes: 3