41

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.

quant
  • 21,507
  • 32
  • 115
  • 211
  • 1
    Which version of C++? – Deduplicator Sep 21 '14 at 10:30
  • @Deduplicator If it's not specified we shall assume C++11. – Shoe Sep 21 '14 at 10:30
  • If you are already using `boost` why not use the MPL? This would be a trivial task. – pmr Sep 21 '14 at 10:31
  • @Deduplicator yeah I'm using C++11. I assumed the `c++` tag just refers to the current version? – quant Sep 21 '14 at 10:31
  • @pmr, if this can be solved with a "trivial task" I'm all ears! – quant Sep 21 '14 at 10:32
  • I think you are looking more for a TMP version of `std::unique`, rather than finding out if a tuple contains a type. – sbabbi Sep 21 '14 at 10:34
  • @quant: Ah no, not quite. It just means C++, not an old dinosaur. And the current version is C++14 now! – Deduplicator Sep 21 '14 at 10:36
  • @quant Either use an `mpl::set` or use a combination of `mpl::sort` and `mpl::unique`. – pmr Sep 21 '14 at 10:38
  • @Deduplicator oh right, forgot about that! Well I've added the appropriate dinosaur tag. – quant Sep 21 '14 at 10:38
  • For clarification: Do you really want to test whether the tuple-type already contains the type, or do you want a tuple-type which adds the type if it does not already contain it? – Deduplicator Sep 21 '14 at 10:41
  • @Deduplicator well both, but I wanted to keep the question as concise as possible so I'm just asking how to _check_ whether the tuple contains a type. – quant Sep 21 '14 at 10:42

7 Answers7

36
#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;
Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
  • I can't understand how it works... Would you mind explaining it? – GingerPlusPlus Sep 21 '14 at 11:14
  • This is very clever! I don't think I'd ever have thought of this. Thanks :) – quant Sep 21 '14 at 11:15
  • @GingerPlusPlus: it recursively strips off the head-type of tuple (leaving only its tail), until either the new head is same as `T` (then it eventually inherits from `true_type` stopping recursion), or the tuple has no more types (then it ends up with inheritance from `false_type`). Note there are specialized cases for `>` (`T` is same as head `T`) and `>` (`T` is different than head `U`). This is very similar to the way the `tuple` is defined itself. – Piotr Skotnicki Sep 21 '14 at 11:38
  • @GingerPlusPlus: That is, `tuple` privately inherits from `tuple` – Piotr Skotnicki Sep 21 '14 at 11:44
  • Good answer, but your constant should probably be a bool value. In c++17 : `template ` `inline constexpr bool tuple_contains_v = has_type::value;` – scx Jan 27 '19 at 17:26
26

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
Ross Bencina
  • 3,822
  • 1
  • 19
  • 33
8

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.

Oktalist
  • 14,336
  • 3
  • 43
  • 63
cdacamara
  • 223
  • 1
  • 7
8

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.

Community
  • 1
  • 1
skypjack
  • 49,335
  • 19
  • 95
  • 187
  • 2
    This is IMO the cleanest (most readable) C++14 solution by far. It is only outshined by the use of folding expression in @Benno Straub 's C++17 answer below. – Smartskaft2 Jan 26 '21 at 15:13
8

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> || ...);
}

template<typename U, typename Tuple>
constexpr inline bool contains_v = contains<U>(std::declval<Tuple>());
Benno Straub
  • 2,268
  • 3
  • 19
  • 21
  • This gives errors: constexpr inline bool tupleHasTypeV = tupleHasType(std::declval(Tuple)); error: expected primary-expression before ‘)’ token constexpr inline bool tupleHasTypeV = tupleHasType(std::declval()); error: call to non-‘constexpr’ function ‘decltype (__declval<_Tp>(0)) std::declval() [with _Tp = std::tuple; decltype (__declval<_Tp>(0)) = std::tuple&&]’ – Nuclear Jan 08 '23 at 21:52
  • @Nuclear It should be `std::declval()`, I updated my answer accordingly – Benno Straub Jan 08 '23 at 23:25
3

Here is a version that does not recursively instantiate the template to check for a matching type. Instead it uses SFINAE with indices-based meta-programming:

#include <type_traits>
#include <tuple>

template <std::size_t... Indices>
struct index_sequence {
    typedef index_sequence<Indices..., sizeof...(Indices)> next;
};

template <std::size_t Start>
struct make_index_sequence {
    typedef typename make_index_sequence<Start - 1>::type::next type;
};

template <>
struct make_index_sequence<0> {
    typedef index_sequence<> type;
};

template <int n>
using make_index_sequence_t = typename make_index_sequence<n>::type;

template <typename Value, typename Sequence>
struct lookup;

template <typename Value, std::size_t... index>
struct lookup<Value, index_sequence<index...>>
{
private:
    struct null;

    template <typename... Args>
    static std::false_type
    apply(std::conditional_t<std::is_convertible<Args, Value>::value, null, Args>...);

    template <typename...>
    static std::true_type apply(...);

    template <typename... Args>
    static auto apply_helper(Args&&...) ->
    decltype(apply<std::remove_reference_t<Args>...>(std::declval<Args>()...));
public:
    template <typename Tuple>
    using value = decltype(
        apply_helper(
            std::declval<
                typename std::tuple_element<index, Tuple>::type
            >()...
        )
    );
};

template <typename Value, typename Tuple>
using has_type = decltype(
    typename lookup<Value,
                    make_index_sequence_t<std::tuple_size<Tuple>::value>
    >::template value<Tuple>{}
);

Live Demo

David G
  • 94,763
  • 41
  • 167
  • 253
1

Since you asked for it, here is a boost::mpl version:

#include <boost/mpl/unique.hpp>
#include <boost/mpl/sort.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/type_traits/is_same.hpp>

using namespace boost;

template<typename Seq>
struct unique_concat : 
  mpl::unique<typename mpl::sort<Seq, is_same<mpl::_1,mpl::_2>>::type, 
              is_same<mpl::_1,mpl::_2>> {};

template<typename T>
struct print;

int main()
{
  typedef mpl::vector<int, float, float, char, int, double, int> input;
  print<unique_concat<input>::type> asdf;

  return 0;
}
pmr
  • 58,701
  • 10
  • 113
  • 156
  • Building with `g++ -Wall -Wextra -pedantic -Wno-sign-compare -Wno-long-long -o tpl_b tuple_boost.cpp` yields an error: `tuple_boost.cpp:19:37: error: aggregate ‘print, 0>, 0>, 0>, 0> > asdf’ has incomplete type and cannot be defined` The specific error: `print::type> asdf;` – David C. Rankin Sep 24 '14 at 01:26
  • I apologize, the version info is `gcc 4.9.1-1` and `boost 1.55.0-6`. – David C. Rankin Sep 24 '14 at 01:42
  • 1
    @DavidC.Rankin This is intended. It is an easy way to see the type name. – pmr Sep 24 '14 at 08:57
  • why sort+unique ? there is already `mpl::has_key`. isn't it what OP wants ? – v.oddou Dec 13 '18 at 07:33