2

I need to check if a value of type T is any of its parameters using a helper function.

For example, using something like the following code

enum class my_enum{k1, k2, k3, k4, k5};
auto v{my_enum::k1};
if (is_any_of(v, my_enum::k1, my_enum::k4, my_enum::k5)) {
}

rather than using if (v == my_enum::k1 || v == my_enum::k4 || v== my_enum::k5) {} or using switch-case.

How do I implement the variadic function bool is_any_of() in C++11?

How the implementation would become simpler with C++17 fold expressions?

aep
  • 1,583
  • 10
  • 18
  • Are you assuming that the types of the arguments could differ and if so, are they supposed to be compared directly to `v` with `==` or should they be converted to `v`'s type first? – walnut Feb 06 '20 at 00:55
  • Types of the arguments may be different and they can directly be compared with `==` - is my assumption. Thanks. – aep Feb 06 '20 at 00:59
  • Not an expert on metaprogramming so I cannot provide a `C++11` version, but the `C++17` one is pretty trivial: `template constexpr bool is_any_of(const my_enum value, Ts&& ... ts) { return ((value == ts) || ...); }`. – Fureeish Feb 06 '20 at 01:01
  • @Fureeish Yes. That works with C++17 fold expressions, more generically with `template constexpr bool is_any_of(T v, Ts&& ... ts) { return ((v == ts) || ...);` thanks for this, but I'm specifically looking for a C++11 solution since I'm currently working on C++11 code base. – aep Feb 06 '20 at 01:14

3 Answers3

3

This will work in C++11 and will do what you want as long as all of the types can be compared to each other.

template<typename T, typename R>
bool is_any_of(T t, R r)
{
   return t == r;
}

template<typename T, typename R, typename... ARGS>
bool is_any_of(T t, R r, ARGS... args)
{
   if (t == r)
   {
      return true;
   }
   else
   {
      return is_any_of(t, args...);
   }
}

This is much more compact and will work in C++17

template<typename T, typename... ARGS>
bool is_any_of(T t, ARGS... args)
{
   return ((t == args) || ...);
}
  • 3
    You should probably take all arguments by forwarding reference and forward them properly. – walnut Feb 06 '20 at 01:32
2

For a non-recursive C++11 alternative:

template <typename T, typename... Ts>
bool is_any_of (T t, Ts&&... ts) {
    std::initializer_list<bool> eq { (t == ts)... };
    return std::any_of(eq.begin(), eq.end(), [](bool i){ return i; });
}

https://godbolt.org/z/K7xtia

But as many as already answered, the fold expression return ((t == ts) || ...); is more compact, expressive, and optimizable.

parktomatomi
  • 3,851
  • 1
  • 14
  • 18
0

You've already received good template based solutions. I had authored one myself, but it duplicated another answer. So, this answer is different, but it also does not use templates. Instead, it uses a variadic macro.

If you need true short-circuit behavior, then you will need to expand out the full expression. Otherwise, if the parameters have side-effects, passing them into a function will trigger all of them. As a contrived example,

template <typename T>
const T & x(const T &v) {
    std::cout << __func__ << ": " << v << '\n';
    return v;
}

  //...
  if (is_any_of(2, x(1), x(2), x(3))) {
    //...
  }

A short-circuit implementation would avoid calling x(3) when the match is detected on the x(2). But, that would require that is_any_if(...) expand to:

  if ((2 == x(1)) || (2 == x(2)) || (2 == x(3))) {
    //...

You can use a macro to accomplish this expansion. Below is one possible implementation, which can take up to 9 parameters to test against.

#define is_any_of(...) \
        (V_(V_(is_any_of_X(__VA_ARGS__,A,9,8,7,6,5,4,3,2,_))(__VA_ARGS__)))

#define is_any_of_X(_A,_9,_8,_7,_6,_5,_4,_3,_2,_1,X,...) is_any_of_##X

#define is_any_of_A(V, X, ...) ((X) == (V)) || is_any_of_9(V, __VA_ARGS__)
#define is_any_of_9(V, X, ...) ((X) == (V)) || is_any_of_8(V, __VA_ARGS__)
#define is_any_of_8(V, X, ...) ((X) == (V)) || is_any_of_7(V, __VA_ARGS__)
#define is_any_of_7(V, X, ...) ((X) == (V)) || is_any_of_6(V, __VA_ARGS__)
#define is_any_of_6(V, X, ...) ((X) == (V)) || is_any_of_5(V, __VA_ARGS__)
#define is_any_of_5(V, X, ...) ((X) == (V)) || is_any_of_4(V, __VA_ARGS__)
#define is_any_of_4(V, X, ...) ((X) == (V)) || is_any_of_3(V, __VA_ARGS__)
#define is_any_of_3(V, X, ...) ((X) == (V)) || is_any_of_2(V, __VA_ARGS__)
#define is_any_of_2(V, X)      ((X) == (V))
#define is_any_of_1(...)       false
#define is_any_of_0(...)       true

#define is_any_of_Y(_1,Y,...) is_any_of_##Y
#define is_any_of__(...) I_(is_any_of_Y, E_ __VA_ARGS__ () 0, 1)(__VA_ARGS__)

#define V_(...) __VA_ARGS__
#define I_(M,...) V_(M(__VA_ARGS__))
#define E_() _,

The technique is explained here. Briefly, it applies a macro argument counting trick, as well as a portable method to detect whether the single argument case is actually the empty argument case. (If you are using GCC, the detection can be simplified.)

jxh
  • 69,070
  • 8
  • 110
  • 193