How do I find out if a tuple contains a type?

Suppose I want to create a compile-time heterogenous container of unique types from some sequence of non-unique types. In order to do this I need to iterate over the source type (some kind of tuple) and check whether each type already exists in my "unique" tuple.

My question is: How can I check whether a tuple (or a boost::fusion container) contains a type?

I'm open to using either the STL or boost.


Solution 1:

#include <tuple>
#include <type_traits>

template <typename T, typename Tuple>
struct has_type;

template <typename T>
struct has_type<T, std::tuple<>> : std::false_type {};

template <typename T, typename U, typename... Ts>
struct has_type<T, std::tuple<U, Ts...>> : has_type<T, std::tuple<Ts...>> {};

template <typename T, typename... Ts>
struct has_type<T, std::tuple<T, Ts...>> : std::true_type {};

DEMO

And an additional alias, if the trait itself should be std::true_type or std::false_type :

template <typename T, typename Tuple>
using tuple_contains_type = typename has_type<T, Tuple>::type;

Solution 2:

In C++17 you can do it like this:

template <typename T, typename Tuple>
struct has_type;

template <typename T, typename... Us>
struct has_type<T, std::tuple<Us...>> : std::disjunction<std::is_same<T, Us>...> {};

In C++11 you have to roll your own or / disjunction. Here's a full C++11 version, with tests:

#include <tuple>
#include <type_traits>

template<typename... Conds>
struct or_ : std::false_type {};

template<typename Cond, typename... Conds>
struct or_<Cond, Conds...> : std::conditional<Cond::value, std::true_type, or_<Conds...>>::type
{};

/*
// C++17 version:
template<class... B>
using or_ = std::disjunction<B...>;
*/  

template <typename T, typename Tuple>
struct has_type;

template <typename T, typename... Us>
struct has_type<T, std::tuple<Us...>> : or_<std::is_same<T, Us>...> {};

// Tests
static_assert(has_type<int, std::tuple<>>::value == false, "test");
static_assert(has_type<int, std::tuple<int>>::value == true, "test");
static_assert(has_type<int, std::tuple<float>>::value == false, "test");
static_assert(has_type<int, std::tuple<float, int>>::value == true, "test");
static_assert(has_type<int, std::tuple<int, float>>::value == true, "test");
static_assert(has_type<int, std::tuple<char, float, int>>::value == true, "test");
static_assert(has_type<int, std::tuple<char, float, bool>>::value == false, "test");
static_assert(has_type<const int, std::tuple<int>>::value == false, "test"); // we're using is_same so cv matters
static_assert(has_type<int, std::tuple<const int>>::value == false, "test"); // we're using is_same so cv matters

Solution 3:

Because nobody posted it, I'm adding one more solution based on the bool trick I've learned about here on SO:

#include<type_traits>
#include<tuple>

template<bool...>
struct check {};

template<typename U, typename... T>
constexpr bool contains(std::tuple<T...>) {
    return not std::is_same<
        check<false, std::is_same<U, T>::value...>,
        check<std::is_same<U, T>::value..., false>
    >::value;
}

int main() {
    static_assert(contains<int>(std::tuple<int, char, double>{}), "!");
    static_assert(contains<char>(std::tuple<int, char, double>{}), "!");
    static_assert(contains<double>(std::tuple<int, char, double>{}), "!");
    static_assert(not contains<float>(std::tuple<int, char, double>{}), "!");
    static_assert(not contains<void>(std::tuple<int, char, double>{}), "!");
}

In terms of compile-time performance it's slower than the accepted solution, but it's worth to mention it.


In C++14 it would be even easier to write. The standard template offers already all what you need to do that in the <utility> header:

template<typename U, typename... T>
constexpr auto contains(std::tuple<T...>) {
    return not std::is_same<
        std::integer_sequence<bool, false, std::is_same<U, T>::value...>,
        std::integer_sequence<bool, std::is_same<U, T>::value..., false>
    >::value;
}

This is not far conceptually from what std::get does (available since C++14 for types), but note that the latter fails to compile if the type U is present more than once in T....
If it fits with your requirements mostly depends on the actual problem.

Solution 4:

I actually needed something like this for a project. This was my solution:

#include <tuple>
#include <type_traits>

namespace detail {
    struct null { };
}

template <typename T, typename Tuple>
struct tuple_contains;

template <typename T, typename... Ts>
struct tuple_contains<T, std::tuple<Ts...>> :
  std::integral_constant<
    bool,
    !std::is_same<
      std::tuple<typename std::conditional<std::is_same<T, Ts>::value, detail::null, Ts>::type...>,
      std::tuple<Ts...>
    >::value
  >
{ };

The main advantage of this method is that it's one instantiation, no recursion required.

Solution 5:

C++17 and up solution using fold expressions:

template<typename U, typename... T>
constexpr bool contains(std::tuple<T...>) {
    return (std::is_same_v<U, T> || ...);
}