34

Is there any way to shorten the condition for this if statement?

int x;
if (x != 3 && x != 8 && x != 87 && x != 9){
  SomeStuff();
}

I'm thinking of something sort of like this:

if (x != 3, 8, 87, 9) {}

But I tried that and it doesn't work. Do I just have to write it all out the long way?

Stargateur
  • 24,473
  • 8
  • 65
  • 91
Aaron Beaudoin
  • 1,107
  • 1
  • 10
  • 23
  • Is there any mathematical pattern between the numbers you want to test? (Can't see one for the example of 3,8,87,9) – Superlokkus Jan 13 '16 at 08:51
  • 1
    @Superlokkus, I don't think so as the OP mentioned in one of the comments below. – CroCo Jan 13 '16 at 09:06
  • 8
    `if (((x - 3) * (x - 8) * (x - 87) * (x - 9)) != 0)` should use less conditions, but may fail with overflow. – Jarod42 Jan 13 '16 at 09:24
  • 6
    @Jarod42 Also these multiplications could AFAIK be more costly than the simply tests for equality. – Superlokkus Jan 13 '16 at 10:50
  • 2
    Typically these random numbers can be described in words _("invalid lengths", "super primes", etc)_, so you can and should pull the check out into a function named `isValidLength` (or whatever), making the conditional both shorter and easier to read. And if the numbers have no relation, they are magic numbers and should be constants. – BlueRaja - Danny Pflughoeft Jan 13 '16 at 13:39
  • 2
    Related: [Better way to say `x == Foo::A || x == Foo::B || x == Foo::C || `](http://stackoverflow.com/q/12240050/1468366) particularly about the case where `x` is of some ugly-to-write type. – MvG Jan 13 '16 at 21:09
  • I don't see nothing wrong with that if your doing it once. If not then declare a boolean function and reuse it to compare a x value. – Jay Harris Jan 13 '16 at 22:02
  • Does short circuit work if you flip != to == and && to || and use else? – Marichyasana Feb 25 '17 at 16:44

8 Answers8

43

If you want to know if an integer is in a given set of integers, then use std::set:

std::set<int> accept { 1, 4, 6, 8, 255, 42 };
int x = 1;

if (!accept.count(x))
{
    // ...
}
Alexis Pierru
  • 342
  • 2
  • 7
32

Just for completeness, I'll offer up using a switch:

switch (x) {
    case 1:
    case 2:
    case 37:
    case 42:
        break;
    default:
        SomeStuff();
        break;
}

While this is pretty verbose, it only evaluates x once (if it is an expression) and probably generates the most efficient code of any solution.

gsamaras
  • 71,951
  • 46
  • 188
  • 305
1201ProgramAlarm
  • 32,384
  • 7
  • 42
  • 56
  • 2
    You don't even need a break; if you put the default case first – Jerry Jeremiah Jan 13 '16 at 10:39
  • 1
    Extract the switch to a function (`static inline`) and use `return true` or `return false`. Then replace the code with `if (matches(x)) SomeStuff();` – aragaer Jan 13 '16 at 13:30
  • 1
    @JerryJeremiah a case label must be applied to a statement. if you ordered the numbered cases last and removed that break you would still need to leave an empty statement (single semicolon). Alternatively you could just remove the break from the default case without rearranging the cases – Steve Cox Jan 13 '16 at 14:56
  • @JerryJeremiah I got into the habit long ago of _always_ putting breaks at the end of my switch statements. It can avoid future problems when someone else comes along and adds more values at the end. – 1201ProgramAlarm Jan 14 '16 at 00:58
29

Here is my solution using variadic template. The runtime performance is as efficient as manually writing x != 3 && x != 8 && x != 87 && x != 9.

template <class T, class U>
bool not_equal(const T& t, const U& u) {
  return t != u;
}

template <class T, class U, class... Vs>
bool not_equal(const T& t, const U& u, const Vs&... vs) {
  return t != u && not_equal(t, vs...);
}

int main() {
  std::cout << not_equal( 3, 3, 8, 87, 9) << std::endl;
  std::cout << not_equal( 8, 3, 8, 87, 9) << std::endl;
  std::cout << not_equal(87, 3, 8, 87, 9) << std::endl;
  std::cout << not_equal( 9, 3, 8, 87, 9) << std::endl;
  std::cout << not_equal(10, 3, 8, 87, 9) << std::endl;
}

Since C++17, the implementation can be simplified with the help of fold expressions:

template <class T, class... Vs>
bool not_equal(const T& t, const Vs&... vs) {
  return ((t != vs) && ...);
}
L. F.
  • 19,445
  • 8
  • 48
  • 82
Lingxi
  • 14,579
  • 2
  • 37
  • 93
  • 3
    `return ((t != us) && ...);` in c++1z. – Jarod42 Jan 13 '16 at 09:11
  • @Jarod42 Could you say something more? I don't quite get your idea. – Lingxi Jan 13 '16 at 09:54
  • 1
    I said that code may be simplified in c++1z [Demo](http://coliru.stacked-crooked.com/a/52385f46a1571206) – Jarod42 Jan 13 '16 at 09:57
  • 2
    @Jarod42 Wow, that's really nice~ – Lingxi Jan 13 '16 at 10:02
  • 1
    I like this idea, but there are some flaws: not only the signature of the function doesn't give any hint about what the function does, but it forces one to go through (simple) recursive variadic templates to understand, which may be annoying for some people. – DarioP Jan 13 '16 at 12:30
14

What about this:

#include <iostream>
#include <initializer_list>
#include <algorithm>

template <typename T>
bool in(const T t, const std::initializer_list<T> & l) {
    return std::find(l.begin(), l.end(), t) != l.end();
}

int main() {
  std::cout << !in(3, {3, 8, 87, 9}) << std::endl;
  std::cout << !in(87, {3, 8, 87, 9}) << std::endl;
  std::cout << !in(10, {3, 8, 87, 9}) << std::endl;
}

or overloading the operator!=:

template<typename T>
bool operator!=(const T t, const std::vector<T> & l) {
    return std::find(l.begin(), l.end(), t) == l.end();
}

int main() {
  std::cout << ( 3!=std::vector<int>{ 3, 8, 87, 9}) << std::endl;
  std::cout << ( 8!=std::vector<int>{ 3, 8, 87, 9}) << std::endl;
  std::cout << (10!=std::vector<int>{ 3, 8, 87, 9}) << std::endl;
}

Unfortunately at the moment parsers do not like to have initializer_list as argument of operators, so it is not possible to get rid of std::vector<int> in the second solution.

Community
  • 1
  • 1
DarioP
  • 5,377
  • 1
  • 33
  • 52
  • +1. The first solution has a good balance between efficiency and verbosity, one of the best presented here. – Jason R Jan 13 '16 at 13:49
  • 2
    bad case for overloading of `!=`. `!!=` should be equivalent to `==`, to avoid confusion, which is obviously not possible here. also, the `!=` operator is expected to be symmetric. this can only add confusion – njzk2 Jan 13 '16 at 19:26
  • 1
    Would +1 if you didn't try overloading the operator. – user541686 Jan 13 '16 at 21:52
  • @njzk2 Yeah, I agree with you: altering the natural meaning and properties of operators is never really good. I was trying to get some syntactic sugar: `x!={a,b,c}` as a compact form of `x!=a && x!=b && x!=c`, which is sound in my opinion, but fails (see the last paragraph), so in the end I would myself avoid the operator. – DarioP Jan 14 '16 at 08:04
5

If you don't want to repeat this condition over and over, then use macro.

#include <iostream>

#define isTRUE(x, a, b, c, d)  ( x != a && x != b && x != c && x != d ) 

int main() 
{
    int x(2);
    std::cout << isTRUE(x,3,8,87,9) << std::endl;

    if ( isTRUE(x,3,8,87,9) ){
        // SomeStuff();
    }
    return 0;
}
CroCo
  • 5,531
  • 9
  • 56
  • 88
  • macros are frowned upon in C++. The template solution essentially does the same and is flexible with the number of arguments. – user23573 Jan 13 '16 at 09:00
  • 1
    @CroCo: example in such case: `isTRUE(heavyComputationIntensive(), 1, 2, 3, 4)` or `isTRUE(a && b, 2, 3, 4, 5)`... – Jarod42 Jan 13 '16 at 09:15
  • @CroCo, there are several reasons (as pointed out [elsewhere](http://stackoverflow.com/questions/14041453/why-are-preprocessor-macros-evil-and-what-are-the-alternatives) ): 1) macros are difficult to debug, 2) macros can have strange side effects, 3) macros are unaware of namespaces, 4) macros have no typesafety. Besides these points: with today's C++14 standard there are very few reasons left why you would actually need macros. (include file protection is about the last one, hopefully be removed by C++17) – user23573 Jan 13 '16 at 12:11
3

If the numbers are not "1,2,3,4" and are, instead, a random number of random integers, then you could put those numbers in a data structure (such as an std::vector), and then iterate over that array using a loop (as suggested below, std::find is a ready-made option).

For example:

#include <algorithm>

int x;
std::vector<int> checknums;
// fill the vector with your numbers to check x against

if (std::find(checknums.begin(), checknums.end(), x) != checknums.end()){
    DoStuff();
}
mech
  • 2,775
  • 5
  • 30
  • 38
3
int A[]={3, 8, 87, 9};

int x;

if (std::find(A, A+4, x)==A+4) SomeStuff();
Joshua
  • 49
  • 5
3

Note that the use of macros is heavily frowned upon by many programmers of C++, since macros are very powerful and have the ability to be frustrating if created improperly. However, if created and used properly and intelligently they can be a major time saver. I don't see another way to get the requested syntax and code space per comparison similar to what you are requesting without compromising the efficiency, code space, or memory used by your program. Your goal was to have a shortcut, and most of the other solutions presented here are longer than what you originally wanted to shorten. Here are macros which will do so safely, assuming you are comparing an integer:

#pragma once
int unused;
#define IFNOTIN2(x, a, b) \
  if (unused = (x) && unused != (a) && unused != (b))
#define IFNOTIN3(x, a, b, c) \
  if (unused = (x) && unused != (a) && unused != (b) && unused != (c))
#define IFNOTIN4(x, a, b, c, d) \
  if (unused = (x) && unused != (a) && unused != (b) && unused != (c) && unused != (d))
#define IFNOTIN5(x, a, b, c, d, e) \
  if (unused = (x) && unused != (a) && unused != (b) && unused != (c) && unused != (d) && unused != (e))

Here is a working tested example with one of the above macros:

#include <iostream>
#include "macros.h"

int main () {
  std::cout << "Hello World\n";

  for (int i = 0; i < 100; i ++) {
    std::cout << i << ": ";
    IFNOTIN4 (i, 7, 17, 32, 87) {
      std::cout << "PASSED\n";
    } else {
      std::cout << "FAILED\n";
    }
  }
  std::cin.get();

  return 0;
}

Note that the macros should go in a header file that's included wherever you need to use them. Your code will fail to compile if you are already using a variable named 'unused' in your code elsewhere or trying to use these macros to compare something other than an integer. I'm sure you can expand the macros to handle other types of data if that becomes necessary.

Order of operations is preserved by using brackets around all inputs, and the value is saved to a variable before comparison to prevent multiple executions of CPU-intensive code.

azoundria
  • 940
  • 1
  • 8
  • 24
  • Yuck. The "in" function taking a std::inititilizer list is *so* much nicer. Your solution fails horribly for: `for (const auto x : range) IFNOTIN(x, 1, 2) return false;` – Martin Bonner supports Monica Jan 13 '16 at 16:39
  • I have to admit I'm not fully familiar with what 'const auto' does. It would be useful to see a full working example. As I stated, the example is only meant to be used in cases where 'x' is an integer, and I believe 'auto' automatically assumes one of multiple types, so does the code even compile? And if it does, I'm trying to understand what it could possibly do that's worse than copying an integer at every iteration. Which I agree is less efficient, but I don't believe 'fails horribly'. – azoundria Jan 15 '16 at 01:15
  • `const auto` can be replaced with `const x`. The problem is that IFNOTIN expands to *two* statements, and the for loop will only loop over the first of them. It will end up behaving like `for (int x: range) { unused = x; } /* next line */ if (unused != 3 ...)` – Martin Bonner supports Monica Jan 15 '16 at 06:48