Compact way to write if(..) statement with many equalities
Is there a better way to write code like this:
if (var == "first case" or var == "second case" or var == "third case" or ...)
In Python I can write:
if var in ("first case", "second case", "third case", ...)
which also gives me the opportunity to easily pass the list of good options:
good_values = "first case", "second case", "third case"
if var in good_values
This is just an example: the type of var
may be different from a string, but I am only interested in alternative (or
) comparisons (==
). var
may be non-const
, while the list of options is known at compile time.
Pro bonus:
- laziness of
or
- compile time loop unrolling
- easy to extend to other operators than
==
if you want to expand it compile time you can use something like this
template<class T1, class T2>
bool isin(T1&& t1, T2&& t2) {
return t1 == t2;
}
template<class T1, class T2, class... Ts>
bool isin(T1&& t1 , T2&& t2, T2&&... ts) {
return t1 == t2 || isin(t1, ts...);
}
std::string my_var = ...; // somewhere in the code
...
bool b = isin(my_var, "fun", "gun", "hun");
I did not test it actually, and the idea comes from Alexandrescu's 'Variadic templates are funadic' talk. So for the details (and proper implementation) watch that.
Edit: in c++17 they introduced a nice fold expression syntax
template<typename... Args>
bool all(Args... args) { return (... && args); }
bool b = all(true, true, true, false);
// within all(), the unary left fold expands as
// return ((true && true) && true) && false;
// b is false
The any_of
algorithm could work reasonably well here:
#include <algorithm>
#include <initializer_list>
auto tokens = { "abc", "def", "ghi" };
bool b = std::any_of(tokens.begin(), tokens.end(),
[&var](const char * s) { return s == var; });
(You may wish to constrain the scope of tokens
to the minimal required context.)
Or you create a wrapper template:
#include <algorithm>
#include <initializer_list>
#include <utility>
template <typename T, typename F>
bool any_of_c(const std::initializer_list<T> & il, F && f)
{
return std::any_of(il.begin(), il.end(), std::forward<F>(f));
}
Usage:
bool b = any_of_c({"abc", "def", "ghi"},
[&var](const char * s) { return s == var; });
Alrighty then, you want Radical Language Modification. Specifically, you want to create your own operator. Ready?
Syntax
I'm going to amend the syntax to use a C and C++-styled list:
if (x in {x0, ...}) ...
Additionally, we'll let our new in operator apply to any container for which begin()
and end()
are defined:
if (x in my_vector) ...
There is one caveat: it is not a true operator and so it must always be parenthesized as it's own expression:
bool ok = (x in my_array);
my_function( (x in some_sequence) );
The code
The first thing to be aware is that RLM often requires some macro and operator abuse. Fortunately, for a simple membership predicate, the abuse is actually not that bad.
#ifndef DUTHOMHAS_IN_OPERATOR_HPP
#define DUTHOMHAS_IN_OPERATOR_HPP
#include <algorithm>
#include <initializer_list>
#include <iterator>
#include <type_traits>
#include <vector>
//----------------------------------------------------------------------------
// The 'in' operator is magically defined to operate on any container you give it
#define in , in_container() =
//----------------------------------------------------------------------------
// The reverse-argument membership predicate is defined as the lowest-precedence
// operator available. And conveniently, it will not likely collide with anything.
template <typename T, typename Container>
typename std::enable_if <!std::is_same <Container, T> ::value, bool> ::type
operator , ( const T& x, const Container& xs )
{
using std::begin;
using std::end;
return std::find( begin(xs), end(xs), x ) != end(xs);
}
template <typename T, typename Container>
typename std::enable_if <std::is_same <Container, T> ::value, bool> ::type
operator , ( const T& x, const Container& y )
{
return x == y;
}
//----------------------------------------------------------------------------
// This thunk is used to accept any type of container without need for
// special syntax when used.
struct in_container
{
template <typename Container>
const Container& operator = ( const Container& container )
{
return container;
}
template <typename T>
std::vector <T> operator = ( std::initializer_list <T> xs )
{
return std::vector <T> ( xs );
}
};
#endif
Usage
Great! Now we can use it in all the ways you would expect an in operator to be useful. According to your particular interest, see example 3:
#include <iostream>
#include <set>
#include <string>
using namespace std;
void f( const string& s, const vector <string> & ss ) { cout << "nope\n\n"; }
void f( bool b ) { cout << "fooey!\n\n"; }
int main()
{
cout <<
"I understand three primes by digit or by name.\n"
"Type \"q\" to \"quit\".\n\n";
while (true)
{
string s;
cout << "s? ";
getline( cin, s );
// Example 1: arrays
const char* quits[] = { "quit", "q" };
if (s in quits)
break;
// Example 2: vectors
vector <string> digits { "2", "3", "5" };
if (s in digits)
{
cout << "a prime digit\n\n";
continue;
}
// Example 3: literals
if (s in {"two", "three", "five"})
{
cout << "a prime name!\n\n";
continue;
}
// Example 4: sets
set <const char*> favorites{ "7", "seven" };
if (s in favorites)
{
cout << "a favorite prime!\n\n";
continue;
}
// Example 5: sets, part deux
if (s in set <string> { "TWO", "THREE", "FIVE", "SEVEN" })
{
cout << "(ouch! don't shout!)\n\n";
continue;
}
// Example 6: operator weirdness
if (s[0] in string("014") + "689")
{
cout << "not prime\n\n";
continue;
}
// Example 7: argument lists unaffected
f( s, digits );
}
cout << "bye\n";
}
Potential improvements
There are always things that can be done to improve the code for your specific purposes. You can add a ni (not-in) operator (Add a new thunk container type). You can wrap the thunk containers in a namespace (a good idea). You can specialize on things like std::set
to use the .count()
member function instead of the O(n) search. Etc.
Your other concerns
-
const
vsmutable
: not an issue; both are usable with the operator - laziness of
or
: Technically,or
is not lazy, it is short-circuited. Thestd::find()
algorithm also short-circuits in the same way. - compile time loop unrolling : not really applicable here. Your original code did not use loops; while
std::find()
does, any loop unrolling that may occur is up to the compiler. - easy to extend to operators other than
==
: That actually is a separate issue; you are no longer looking at a simple membership predicate, but are now considering a functional fold-filter. It is entirely possible to create an algorithm that does that, but the Standard Library provides theany_of()
function, which does exactly that. (It's just not as pretty as our RLM 'in' operator. That said, any C++ programmer will understand it easily. Such answers have already been proffered here.)
Hope this helps.
First, I recommend using a
for
loop, which is both the easiest and most readable solution:for (i = 0; i < n; i++) { if (var == eq[i]) { // if true break; } }
However, some other methods also available, e.g., std::all_of
, std::any_of
, std::none_of
(in #include <algorithm>
).
Let us look at the simple example program which contains all the above keywords
#include <vector>
#include <numeric>
#include <algorithm>
#include <iterator>
#include <iostream>
#include <functional>
int main()
{
std::vector<int> v(10, 2);
std::partial_sum(v.cbegin(), v.cend(), v.begin());
std::cout << "Among the numbers: ";
std::copy(v.cbegin(), v.cend(), std::ostream_iterator<int>(std::cout, " "));
std::cout << '\\n';
if (std::all_of(v.cbegin(), v.cend(), [](int i){ return i % 2 == 0; }))
{
std::cout << "All numbers are even\\n";
}
if (std::none_of(v.cbegin(), v.cend(), std::bind(std::modulus<int>(),
std::placeholders::_1, 2)))
{
std::cout << "None of them are odd\\n";
}
struct DivisibleBy
{
const int d;
DivisibleBy(int n) : d(n) {}
bool operator()(int n) const { return n % d == 0; }
};
if (std::any_of(v.cbegin(), v.cend(), DivisibleBy(7)))
{
std::cout << "At least one number is divisible by 7\\n";
}
}