Reputation: 23052
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
.
Upvotes: 43
Views: 15363
Reputation: 2557
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> || ...);
}
assert( contains<int >(std::declval<std::tuple<int, float>>()));
assert( contains<float >(std::declval<std::tuple<int, float>>()));
assert(!contains<double>(std::declval<std::tuple<int, float>>()));
//// In case std::declval ever becomes constexpr, the following should work as well:
// template<typename U, typename Tuple>
// constexpr inline bool contains_v = contains<U>(std::declval<Tuple>());
Upvotes: 12
Reputation: 223
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.
Upvotes: 8
Reputation: 4173
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
Upvotes: 28
Reputation: 50540
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.
Upvotes: 8
Reputation: 48447
#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 {};
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;
Upvotes: 37
Reputation: 96810
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>{}
);
Upvotes: 3
Reputation: 59811
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;
}
Upvotes: 1