简体   繁体   中英

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 .

#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;

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

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.

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.

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

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM