How does one filter duplicate types from a tuple?
For example:
using Tuple = std::tuple
usi
#include <type_traits>
#include <tuple>
template <typename T, typename... Ts>
struct unique : std::type_identity<T> {};
template <typename... Ts, typename U, typename... Us>
struct unique<std::tuple<Ts...>, U, Us...>
: std::conditional_t<(std::is_same_v<U, Ts> || ...)
, unique<std::tuple<Ts...>, Us...>
, unique<std::tuple<Ts..., U>, Us...>> {};
template <typename... Ts>
using unique_tuple = typename unique<std::tuple<>, Ts...>::type;
DEMO
Piotr's code is very concise and should be prefered. Here an extended version for general variadic templated classes which works on C++17 (e.g. std::variant
or custom containers):
#include <type_traits>
// end of recursive call: tuple is forwared using `type`
template <typename T, typename... Ts>
struct unique_impl {using type = T;};
// recursive call: 1. Consumes the first type of the variadic arguments,
// if not repeated add it to the tuple.
// 2. Call this again with the rest of arguments
template <template<class...> class Tuple, typename... Ts, typename U, typename... Us>
struct unique_impl<Tuple<Ts...>, U, Us...>
: std::conditional_t<(std::is_same_v<U, Ts> || ...)
, unique_impl<Tuple<Ts...>, Us...>
, unique_impl<Tuple<Ts..., U>, Us...>> {};
// forward definition
template <class Tuple>
struct unique_tuple;
// class specialization so that tuple arguments can be extracted from type
template <template<class...>class Tuple, typename... Ts>
struct unique_tuple<Tuple<Ts...>> : public unique_impl<Tuple<>, Ts...> {};
If you need C++11 you just need to replace the fold expression (std::is_same_v<U, Ts> || ...)
to use a self-made disjunction<...>
(see cppreference possible implementation).
This shoudld work:
template <class Haystack, class Needle>
struct contains;
template <class Car, class... Cdr, class Needle>
struct contains<std::tuple<Car, Cdr...>, Needle> : contains<std::tuple<Cdr...>, Needle>
{};
template <class... Cdr, class Needle>
struct contains<std::tuple<Needle, Cdr...>, Needle> : std::true_type
{};
template <class Needle>
struct contains<std::tuple<>, Needle> : std::false_type
{};
template <class Out, class In>
struct filter;
template <class... Out, class InCar, class... InCdr>
struct filter<std::tuple<Out...>, std::tuple<InCar, InCdr...>>
{
using type = typename std::conditional<
contains<std::tuple<Out...>, InCar>::value
, typename filter<std::tuple<Out...>, std::tuple<InCdr...>>::type
, typename filter<std::tuple<Out..., InCar>, std::tuple<InCdr...>>::type
>::type;
};
template <class Out>
struct filter<Out, std::tuple<>>
{
using type = Out;
};
template <class T>
using without_duplicates = typename filter<std::tuple<>, T>::type;
[Live example]
It works by iteratively constructing the output tuple. Before each type is added, check (using the predicate contains
) whether it's already in the output tuple or not. If not, it's added (the "else" branch of std::conditional
), otherwise it's not added (the "then" branch of std::conditional
).
If you have access to Boost, then this can be done directly using boost::mp11::mp_unique<your_tuple_type>
.
For example:
{
using not_unique = std::tuple<int, int, char, std::string, double, int>;
using filtered = boost::mp11::mp_unique<not_unique>;
static_assert(std::is_same_v<std::tuple<int, char, std::string, double>, filtered>);
}
{
using already_unique = std::tuple<int, char, std::string, double>;
using filtered = boost::mp11::mp_unique<already_unique>;
static_assert(std::is_same_v<std::tuple<int, char, std::string, double>, filtered>);
}
Live example