30

A few times in my program, I've had to check if a variable was one of many options. For example

if (num = (<1 or 2 or 3>)) { DO STUFF }

I've messed around with 'OR's, but nothing seems to be right. I've tried

if (num == (1 || 2 || 3))

but it does nothing.

I'd like to conveniently distinguish between several groups. For example

if (num = (1,2,3))

else if (num = (4,5,6))

else if (num = (7,8,9))
cigien
  • 57,834
  • 11
  • 73
  • 112
Matt Reynolds
  • 787
  • 2
  • 9
  • 22
  • 4
    Is `if (num == 1 || num == 2 || num == 3)` or `if (num >= 1 && num <= 3)` too much typing? If it's longer, you can always make some sort of array and use `std::find`. – chris Mar 03 '13 at 01:49
  • 2
    See http://stackoverflow.com/q/14368525/726361 – Seth Carnegie Mar 03 '13 at 01:50

7 Answers7

73

Here's a way in C++11, using std::initializer_list:

#include <algorithm>
#include <initializer_list>

template <typename T>
bool is_in(const T& v, std::initializer_list<T> lst)
{
    return std::find(std::begin(lst), std::end(lst), v) != std::end(lst);
}

with that, you can do:

if (is_in(num, {1, 2, 3})) { DO STUFF }

It is not very efficient though when not used with built-in types. int will work fine, but if you compare std::string variables for example, the produced code is just awful.

In C++17 however, you can instead use a much more efficient solution that works well with any type:

template<typename First, typename ... T>
bool is_in(First &&first, T && ... t)
{
    return ((first == t) || ...);
}

// ...

// s1, s2, s3, s4 are strings.
if (is_in(s1, s2, s3, s4)) // ...

The C++11 version would be very inefficient here, while this version should produce the same code as hand-written comparisons.

Nikos C.
  • 50,738
  • 9
  • 71
  • 96
  • That looks convenient, but I'm not using 11. I'm just in plain ol' C++. – Matt Reynolds Mar 03 '13 at 12:24
  • 20
    +1 for the stupendously good way to do this in C++17. I would +2 if I could because you bothered to come back 5.5 years later and add that! – underscore_d Sep 19 '18 at 21:19
  • 8
    The c++17 feature is called [fold expression](https://en.cppreference.com/w/cpp/language/fold) – GetFree Oct 01 '18 at 17:29
  • 2
    The `is_in` template inline function is great! I recommend though to declare it with the following signature: `bool is_in(const First& first, const T& ... t)`. If there really is an `operator==` in any of your classes, that needs to receive at least one of its argument as a writeable rvalue reference, then the signature would be correct, but the arguments in the function body should be forwarded via `std::forward(first)` and `std::forward(t)`. – Kai Petzke Mar 18 '19 at 14:13
  • @KaiPetzke Right. Perfect forwarding seems unnecessary here, as `const&` binds just fine to temporaries and extends their lifetime for as long as required. – underscore_d Dec 10 '19 at 14:10
  • The C++17 answer sacrifices readability for performance, +1 for the C++11 one. – kingsjester Jun 05 '23 at 09:39
13

If the values you want to check are sufficiently small, you could create a bit mask of the values that you seek and then check for that bit to be set.

Suppose, you care about a couple of groups.

static const unsigned values_group_1 = (1 << 1) | (1 << 2) | (1 << 3);
static const unsigned values_group_2 = (1 << 4) | (1 << 5) | (1 << 6);
static const unsigned values_group_3 = (1 << 7) | (1 << 8) | (1 << 9);    
if ((1 << value_to_check) & values_group_1) {
  // You found a match for group 1
}
if ((1 << value_to_check) & values_group_2) {
  // You found a match for group 2
}
if ((1 << value_to_check) & values_group_3) {
  // You found a match for group 3
}

This approach works best for values that don't exceed the natural size your CPU likes to work with. This would typically be 64 in modern times, but may vary depending upon the specifics of your environment.

Eric Johnson
  • 696
  • 4
  • 23
  • Cool. That makes thing easy. If I wanted to see "if 1; else if 2, else if 3;" how would I do that? Would I need a new bit mask, or could I just add in "(2 << n)"? – Matt Reynolds Mar 03 '13 at 12:37
  • If you want to check values 1, 2 and 3, then your bit mask would become this instead values_i_like = (1<<1) | (1<<2) | (1<<3);. – Eric Johnson Mar 03 '13 at 12:48
  • no, you misunderstand. I want to be able to differ from (1,2,3) and (4,5,6) – Matt Reynolds Mar 03 '13 at 12:51
  • values_i_like = (1<<1) | (1<<2) | (1<<3) | (1<<4) | (1<<5) | (1<<6); – Eric Johnson Mar 03 '13 at 12:54
  • but how would you distinguish from (1,2,3) and (4,5,6)? – Matt Reynolds Mar 03 '13 at 12:55
  • Here, let me add an example into the original question. – Matt Reynolds Mar 03 '13 at 12:55
  • Okay, so you would have to use multiple sets. Thanks! – Matt Reynolds Mar 03 '13 at 13:05
  • Just to be overly clear, you only need to use multiple sets IFF the logic that is in the "then" side of the test is different across sets. If the logic is the same, then use one set. Also, if you aren't familiar with the bit shift operators, do spend some time learning them. Copying code is fine. Copying what looks like magic is bad. – Eric Johnson Mar 03 '13 at 13:09
  • Yes, that's exactly what I'm doing, the logic differs. Also, I kind of skimmed that section when I was learning C++, but I went back now and it's clear. Thanks again! – Matt Reynolds Mar 03 '13 at 13:26
  • This won't typically be 64, even in modern times: both MSVS and GCC have `sizeof(int)==4` (and `CHAR_BIT==8`), so 32 is much more likely to have, even on x86_64. – Ruslan Dec 21 '16 at 10:08
  • Please down-vote this answer, this is not readable.. – kingsjester Jun 05 '23 at 09:36
9

You have to do the comparison with each value. E.g.

if (num == 1 || num == 2 || num == 3) { stuff }

You may also want to consider a switch and intentionally falling through cases (although I don't think it's the best solution for what you're stating).

switch (num) {
    case 1:
    case 2:
    case 3:
        {DO STUFF}
        break;

    default:
        //do nothing.
}
john.pavan
  • 910
  • 4
  • 6
  • 1
    Yeah, not exactly what I'm looking for. Thanks anyways! – Matt Reynolds Mar 03 '13 at 02:11
  • 2
    Umm, no, you don't _have_ to do that. – einpoklum Nov 26 '16 at 10:17
  • `switch` can only compare an integral or enumerator type, though, so while it does work - and often neatly - for such objects, it quickly reveals itself to be a one-trick pony, a relic of the assembler jump-table days, and not a general solution (and therefore not suitable for generic code, which is pretty important). – underscore_d Sep 19 '18 at 21:21
8

I just had a similar problem and I came to these C++11 solutions:

template <class T> 
struct Is 
{ 
  T d_; 
  bool in(T a) { 
    return a == d_; 
  } 
  template <class Arg, class... Args> 
  bool in(Arg a, Args... args) { 
    return in(a) || in(args...); 
  } 
}; 

template <class T> 
Is<T> is(T d) { 
  return Is<T>{d}; 
}

Or as alternative without the recursion terminating method. Be aware that here the order of comparisons is undefined and that this does not terminate early if the first match is found. But the code is more compact.

template <class T>
struct Is {
  const T d_;
  template <class... Args>
  bool in(Args... args) {
    bool r{ false }; 
    [&r](...){}(( (r = r || d_ == args), 1)...);
    return r;
  }
};

template <class T>
Is<T> is(T d) { 
  return Is<T>{d}; 
}

So for both solutions the code would look like:

if (is(num).in(1,2,3)) {
  // do whatever needs to be done
}
Felix Petriconi
  • 675
  • 5
  • 11
6

You can define a set of integers, add the desired values to it, and then use the find method to see if the value in question is in the set

std::set<int> values;
// add the desired values to your set...
if (values.find(target) != values.end())
    ...
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • That's an interesting way of doing things... I'll give it a try, thanks! – Matt Reynolds Mar 03 '13 at 02:10
  • Sorry, I'm just getting a ton of errors. How exactly do you use this? Could you give an example? – Matt Reynolds Mar 03 '13 at 02:13
  • 2
    lol, genius in its simplicity! @TheWalkingCactus just use `count` instead with a `set`. –  Jun 27 '14 at 20:46
  • You *can*, but that's an awful lot of dynamic allocation, copying, and immediate destruction for a task so immensely transient and possibly frequent. That's compounded by how 'check if a value is in a set' is such a basic concept that it (A) arguably should be a basic feature of the language and (B) should be free to do. I guess (A) is not really needed when there are easy ways to write a helper function to do it, as others have shown, while also satisfying (B). – underscore_d Sep 19 '18 at 21:25
  • @underscore_d unless we realize that globals exist and are perfect for cases like this. –  Dec 05 '19 at 16:49
  • 2
    @Sahsahae I disagree. For the case of comparing against a small set of known values as a one-off, a set or any other dynamically allocated container is wasteful, and adding a global variable is irresponsible. For the actual question asked, comparing to a set of known constants, a variadic `in()` function using fold expressions is far superior. – underscore_d Dec 10 '19 at 14:11
  • 2
    Note that, from C++20 onwards, there is [`std::set.contains()`](https://en.cppreference.com/w/cpp/container/set/contains). – Adrian Mole Sep 03 '21 at 15:06
5

I needed to do something similar for enums. I have a variable and wish to test it against a ranges of values.

Here I've used a variadic template function. Note the specialisation for const char* type, so that is_in( my_str, "a", "b", "c") has the expected outcome for when my_str stores "a".

#include <cstring> 

template<typename T>
constexpr  bool is_in(T t, T v) {
  return t == v;
}

template<>
constexpr  bool is_in(const char* t, const char* v) {
  return std::strcmp(t,v);
}

template<typename T, typename... Args>
constexpr bool is_in(T t, T v, Args... args) {
  return  t==v || is_in(t,args...);
}

Example usage:

enum class day
{
  mon, tues, wed, thur, fri, sat, sun
};

bool is_weekend(day d)
{
  return is_in(d, day::sat, day::sun);
}
Darren Smith
  • 2,261
  • 16
  • 16
  • 1
    The specialisation for C strings is a nice touch (as it is hard to think when you would want the default behaviour of comparing pointers to that type), as is the presence of a concise and practical example! – underscore_d Sep 19 '18 at 21:27
-3
float n;
if (n<1) exit(0);  
if (n / 3 <= 1)  
   // within 1, 2, 3
else if (n / 3 <= 2)  
   // within 4, 5, 6  
else if (n / 3 <= 3)  
   // within 7, 8, 9