cxxl
cxxl

Reputation: 5399

Check for equality against multiple values in C++

I am looking for an easy, fast and descriptive way in C++ to check if a value is contained in a fixed set of other values. Like in Python, where one can write

if some_function() in (2, 3, 5, 7, 11):
    do_something()

Some obvious options are:

  1. switch/case: If the values in question are integers, then one can write something like this:

    switch (some_function()) {
        case 2: case 3: case 5: case 7: case 11: do_something();
    }
    

    Unfortunately, this only works for integers, and I daresay it's not very pretty.

  2. Use a local variable to keep the temporary result:

    const auto x = some_function();
    if (x == 2 || x == 3 || x == 5 || x == 7 || x == 11) do_something();
    

    I would like to avoid the named temporary variable. Furthermore, this is tedious to write and error-prone.

  3. Use std::set: This can be written (at least in C++20) as:

    if (std::set({ 2, 3, 5, 7, 11 }).contains(some_function())) do_something();
    

    That's kinda nice, but I fear it has some heavy STL overhead.

Are there other, cheaper methods? Maybe some variadic template solution?

Upvotes: 4

Views: 1031

Answers (3)

cxxl
cxxl

Reputation: 5399

Some data on the fast part of the question:

I compiled the three obvious solutions and the one by @cigien with Visual Studio 2019 (C++ Compiler 19.27.29122) with full optimizations (/Ox /std:c++latest) and looked at the assembler output. Here is what I found:

  1. This solution turns into a sweet BT instruction (x64) or a jump table (x86), both only some 10 lines of assembler.

  2. Again a BT instruction (x64) or the obvious 5 comparisons in a row (x86), again about 10 lines.

  3. Speedwise, this is the horror: 120 lines with 9 calls to other functions, all in all 1500 (!) lines. Plus, it requires exceptions and C++20.

  4. @cigien's solution: the same as 2. for both x64 and x86.

Bottom line: the template version is not only short and expressive, but also small and fast.

Upvotes: 2

if it's about style all this stuff has nothing to do with c/c++ anyway.
if it's about performance

#include <iostream>
#include <algorithm>
#include <execution>

int main(int argc , char *argv[])
{
    int b[] = {1 ,4 ,7 ,8};
    int c = 4;
    bool e = std::any_of(std::execution::par, b, &b[0] + sizeof(b)/sizeof(int), [=](int a){ return a == c; });
    if (e) std::cout << "cool " << std::endl;
}

c++ -O3 --std=c++17 a.cpp -o t -ltbb

Upvotes: 0

cigien
cigien

Reputation: 60440

Yes, you can indeed write a variadic template function, combined with a fold-expression, like this:

namespace my 
{
  template<typename T, typename ... Vals>
  bool any_of(T t, Vals ...vals)
  {
     return (... || (t == vals));
  }
}  

and then use it like this:

if (my::any_of(some_function(), 2, 3, 5, 7, 11))
{
    do_something();
}

Note that I've put any_of in a namespace so as to avoid any confusion with std::any_of which is a completely different function.

Upvotes: 8

Related Questions