It is not perfectly clear if OP wants to care of number of occurrences (like subject suggests - "unordered list", or not - like type_set_eq
suggests.
So, I will present both variants.
Starting from set - number of occurrences not important, then the algorithm is as follows:
- For each type from T1 check if it is present in T2
- For each type from T2 check if it is present in T1
Both points are important - because when checking only point 1 - we have counterexample of empty T1 list that would be equal to anything and, of course, the same counterexample for point 2 (the points are symmetric).
To check presence of one type in some list of types - use this simple class template:
template <typename V, typename ...T> struct is_present;
template <typename V> // <- type is not present in empty list
struct is_present<V> : std::false_type {};
template <typename V, typename F, typename ...T>
struct is_present<V,F,T...> : std::integral_constant<bool,
// type is present in non-empty list
// if it is first element
std::is_same<V,F>::value
// or it is present in the remaining list-but-first
|| is_present<V,T...>::value> {};
Since we are in pre-C++17 era - then fold expression are not yet available, so something like this below is necessary to represent fold-and:
template <bool ...v> struct all_trues;
template <> struct all_trues<> : std::true_type {};
template <bool f, bool ...v> struct all_trues<f,v...> :
std::integral_constant<bool,
f && all_trues<v...>::value>
{};
Then definition of comparing for equality of two set of types is as follow:
template <typename ...T1>
struct are_set_of_types
{
template <typename ...T2>
struct equal_to : all_trues<is_present<T1, T2...>::value..., /*1*/
is_present<T2, T1...>::value...> /*2*/
{};
};
It can be done with std::tuple
as OP started to implement, in this way:
template <typename T1, typename T2>
struct type_set_eq;
template <typename ...T1, typename ...T2>
struct type_set_eq<std::tuple<T1...>, std::tuple<T2...>>
: are_set_of_types <T1...>::template equal_to<T2...>
{};
When number of occurrences matters, then algorithm is as follow:
- Check that sizes of both sequences are equal
- Check that occurrence number of each value from left sequence is equal to number of occurrences of this value in second sequence
These 2 points should guarantee that sequences are equal without regards to order of elements.
So, the difference from set-comparison is in this class template:
template <typename V, typename ...T>
struct count_occurences;
template <typename V>
// number of occurrences in empty list is 0
struct count_occurences<V> : std::integral_constant<std::size_t, 0u> {};
template <typename V, typename F, typename ...T>
// number of occurrences in non-empty list is
struct count_occurences<V,F,T...> : std::integral_constant<std::size_t,
// 1 if type is same as first type (or 0 otherwise)
(std::is_same<V,F>::value ? 1u : 0u)
// plus number of occurrences in remaining list
+ count_occurences<V,T...>::value> {};
And the template to check two sequences equality w/o regards to order:
template <typename ...T1>
struct are_unordered_types_sequences
{
// when number of occurrences is important
template <typename ...T2>
struct equal_to : all_trues<
/*1*/ sizeof...(T1) == sizeof...(T2),
/*2*/ (count_occurences<T1, T1...>::value == count_occurences<T1, T2...>::value)...>
{};
};
And tuple variant:
template <typename T1, typename T2>
struct type_set_eq;
template <typename ...T1, typename ...T2>
struct type_set_eq<std::tuple<T1...>, std::tuple<T2...>>
: are_unordered_types_sequences<T1...>::template equal_to<T2...>
{};
Wandbox example I used to play with these templates
x
from the first tuple, look forx
in the second tuple and remove if it found. Repeat until the first tuple is empty or until you don't findx
in the second one. – Hakoget<T>
was SFINAE friendly, there would be a neater solution. – Ora<int,int,float>
equal to<float,int>
? I mean, do you want to ignore not only order or values but also their number of occurences? – Woken