How to Combine Functions with Logical Operators in C++
In C++, most STL algorithms can use one function to perform their job on a collection. For example, to extract all the even numbers from a collection, we can write code like this:
auto const numbers = std::vector<int>{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; auto results = std::vector<int>{}; std::copy_if(begin(numbers), end(numbers), back_inserter(results), isMultipleOf2);
Assuming that we have a function isMultipleOf2
:
bool isMultipleOf2(int n) { return (n % 2) == 0; }
(In this particular case it would have been easier to use a lambda, but the purpose it to have simple function to illustrate the point coming up, which applies to more complex functions such as those used in the implementation of the word counter of camel case symbols).
But C++ doesn’t accommodate combinations of functions. For example, if we also have a function isMultipleOf3
and would like to extract the numbers that are either multiple of 2 or multiple of 3, it would be nice to write code like this:
std::copy_if(begin(numbers), end(numbers), back_inserter(results), isMultipleOf2 || isMultipleOf3);
But this doesn’t compile: there is no such thing as an operator||
on functions in C++.
The simplest way that the C++ standard offers (since C++11) is to use a lambda:
std::copy_if(begin(numbers), end(numbers), back_inserter(results), [](int number){ return isMultipleOf2(number) || isMultipleOf3(number); });
This compiles and does extract the numbers that are either multiple of 2 or multiple of 3 from the collection.
But by doing this, the code got more noise:
- the syntax of the lambda: the brackets
[]
, the parameters list, the braces{...}
, etc. - the parameter:
number
.
Indeed, we don’t need to know about the individual parameters passed to the function object. The purpose of the algorithm is to rise the level of abstraction and put it at the level of the collection. We want the code to express that we extract such types of number from the collection, not what we do to individual numbers. Even though it will come to the same result during execution, this is not the right level of abstraction in the code.
You may think that using a lambda in this case is fine. But in case you’re annoyed by the additional code they lead us to write, let’s explore other ways to combine functions with logical operators such as ||
.
I don’t claim that those techniques are better than lambda, they all have their advantages and drawbacks. In any case exploring is instructive. And if you have any piece of feedback I’d love to hear it in the comment section.
Solution #1: designing a combining function
I don’t think there is a way to write an operator||
for functions in the general case, in order to be able to write isMultipleOf2 || isMultipleOf3
. Indeed, functions in the general sense include lambdas, and lambdas can be of any type. So such an operator would be an operator||
for all types. This would be way too intrusive for the rest of the code.
If we can’t have an operator||
, let’s design a function to replace it. We can name it something close to the word “or”. We can’t name it “or” because this name is already reserved by the language. We can either put it in a namespace, or call it something else.
It would be reasonable to put such a general name in a namespace in order to avoid collisions. But for the purpose of the example, let’s just call it or_
here. The target usage of or_
would be this:
std::copy_if(begin(numbers), end(numbers), back_inserter(results), or_(isMultipleOf2, isMultipleOf3));
How should we implement it? I suggest you try to give it a go on your own before reading on.
…
…
…
or_
is a function that takes two functions and that returns a function. We can implement it by returning a lambda:
template<typename Function1, typename Function2> auto or_(Function1 function1, Function2 function2) { return [function1, function2](auto const& value){ return function1(value) || function2(value); }; }
We’ve made the choice of taking the parameter of the lambda by const&
. This is because with STL algorithms, stateless is stressless, which means that everything is simpler when function objects don’t have side effects in STL algorithms, in particular predicates like we have here.
Solution #2: operator||
on a specific type
Let’s try to put back operator||
in the syntax. The problem we had with operator||
was that we couldn’t implement it for all types.
We can work around this constraint by fixing a type:
template<typename Function> struct func { explicit func(Function function) : function_(function){} Function function_; };
We can then define an operator||
for that type, and it won’t collide with other types in the code:
template<typename Function1, typename Function2> auto operator||(func<Function1> function1, Function2 function2) { return [function1, function2](auto const& value){ return function1.function_(value) || function2(value); }; }
The resulting code has the advantage to have ||
in its syntax, but the drawback to show the func
construct:
std::copy_if(begin(numbers), end(numbers), back_inserter(results), func(isMultiple(2)) || isMultiple(3));
Maybe we can find a better name for func
though, if you have any suggestion please write a comment below.
Solution #3: Using Boost Phoenix
The purpose of the Boost Phoenix library is to write complex function object with simple code! If you’re not familiar with Boost Phoenix, you can check out the introduction to Boost Phonix to see the type of code it allows to write.
Boost Phoenix, although an impressive library, can’t work miracles and doesn’t make our initial target code (isMultipleOf2 || isMultipleOf3
) compile. What it allows is to use create objects from isMultipleOf2
and isMultipleOf3
, that will be compatible with the rest of the library.
Boost Phoenix doens’t use macros in general, but for this specific case it does:
BOOST_PHOENIX_ADAPT_FUNCTION(bool, IsMultipleOf2, isMultipleOf2, 1) BOOST_PHOENIX_ADAPT_FUNCTION(bool, IsMultipleOf3, isMultipleOf3, 1)
The first line creates IsMultipleOf2
from isMultipleOf2
, and we have to indicate that isMultipleOf2
returns bool
and takes 1
parameter.
We can then use them this way (with the complete code to show what file to #include
):
#include <boost/phoenix/phoenix.hpp> #include <vector> bool isMultipleOf2(int n) { return (n % 2) == 0; } bool isMultipleOf3(int n) { return (n % 3) == 0; } BOOST_PHOENIX_ADAPT_FUNCTION(bool, IsMultipleOf2, isMultipleOf2, 1) BOOST_PHOENIX_ADAPT_FUNCTION(bool, IsMultipleOf3, isMultipleOf3, 1) int main() { auto const numbers = std::vector<int>{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; auto results = std::vector<int>{}; using boost::phoenix::arg_names::arg1; std::copy_if(begin(numbers), end(numbers), back_inserter(results), IsMultipleOf2(arg1) || IsMultipleOf3(arg1)); }
The price to pay for the nice syntax using ||
is the apparition of arg1
, which means the first argument passed to those functions. In our case, the objects successively passed to this function are the elements inside of the collection numbers
.
What do you think about those techniques to combine multiple functions with logical operations? Do you see other ways to write this with more expressive code?
You will also like
- Introduction to Boost Phoenix
- Making code expressive with lambdas
- It all comes down to respecting levels of abstraction
Share this post!