Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I check type T is among parameter pack Ts...?

I want to write a function to return true if T is one of Ts...

template<class T, class... Ts>
bool is_one_of<T, Ts...>();

For example, is_one_of<int, double, int, float> returns true, and is_one_of<int, double, std::string, bool, bool> returns false.

My own implementation is

template<class T1, class T2>
bool is_one_of<T1, T2>() {
    return std::is_same<T1, T2>;
}

template<class T1, class T2, class... Ts>
bool is_one_of<T1, T2, Ts...>() {
    if (std::is_same<T1, T2>) {
        return true;
    }
    else {
        return is_one_of<T1, Ts...>();
    }
}

This check seems common to me so I wonder if there's already such a function in the standard library.

like image 239
Yixing Liu Avatar asked Jun 23 '19 00:06

Yixing Liu


4 Answers

In your own implementation, one issue is that C++ doesn't allow partial specialization on function templates.

You can use the fold expression (which is introduced in C++17) instead of recursive function call.

template<class T1, class... Ts>
constexpr bool is_one_of() noexcept {
    return (std::is_same_v<T1, Ts> || ...);
}

If you are using C++11 where fold expression and std::disjunction are not available, you can implement is_one_of like this:

template<class...> struct is_one_of: std::false_type {};
template<class T1, class T2> struct is_one_of<T1, T2>: std::is_same<T1, T2> {};
template<class T1, class T2, class... Ts> struct is_one_of<T1, T2, Ts...>: std::conditional<std::is_same<T1, T2>::value, std::is_same<T1, T2>, is_one_of<T1, Ts...>>::type {};
like image 51
Shaoyu Chen Avatar answered Nov 11 '22 08:11

Shaoyu Chen


You can also use std::disjunction to avoid unnecessary template instantiation:

template <class T0, class... Ts>
constexpr bool is_one_of = std::disjunction_v<std::is_same<T0, Ts>...>;

After a matching type is found, the remaining templates are not instantiated. In contrast, a fold expression instantiates all of them. This can make a significant difference in compile time depending on your use case.

like image 27
L. F. Avatar answered Nov 11 '22 09:11

L. F.


Check if type T is among parameter pack Ts:

template<class T0, class... Ts>
constexpr bool is_one_of = (std::is_same<T0, Ts>{}||...);

template variable.

Alternative:

template<class T0, class... Ts>
constexpr std::integral_constant<bool,(std::is_same<T0, Ts>{}||...)> is_one_of = {};

Which has subtle differences; its type carries its value and it is stateless, instead of being a constexpr bool value.

like image 13
Yakk - Adam Nevraumont Avatar answered Nov 11 '22 09:11

Yakk - Adam Nevraumont


The other answers show several correct solutions to solve this specific problem in a clean and concise way. Here is a solution that is not recommended for this specific problem, but demonstrates an alternate technique: In constexpr functions you can use plain for loops and simple logic in order to compute results at compile time. This allows to get rid of the recursion and the attempted partial template specialization of OP's code.

#include <initializer_list>
#include <type_traits>

template<class T, class... Ts>
constexpr bool is_one_of() {
  bool ret = false;

  for(bool is_this_one : {std::is_same<T, Ts>::value...}) {
    ret |= is_this_one;// alternative style: `if(is_this_one) return true;`
  }

  return ret;
}

static_assert(is_one_of<int, double, int, float>(), "");
static_assert(!is_one_of<int, double, char, bool, bool>(), "");

Requires at least C++14.

like image 2
Julius Avatar answered Nov 11 '22 08:11

Julius