Combining functions with logical operators in C ++

In anticipation of the start of classes in a new group stream “C ++ Developer” prepared a translation of interesting material.


Most STL algorithms C ++ uses only one function to do some work on the collection. For example, to extract all even numbers from a collection, we can write code like this:

auto const numbers = std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto results = std::vector{};

std::copy_if(begin(numbers), end(numbers), back_inserter(results), isMultipleOf2);

Assuming we have a function isMultipleOf2:

bool isMultipleOf2(int n)
{
    return (n % 2) == 0;
}

(In this particular case, it would be easier to use a lambda, but for my purposes it is more convenient to write a simple function to illustrate the following thought, which applies to more complex functions, such as those used to implement a word counter in camel notation).

But C ++ does not support function combinations. For example, if we also have the function isMultipleOf3 and we want to extract numbers that are multiples of 2 or multiples of 3, it would be quite simple to write this code:

std::copy_if(begin(numbers), end(numbers), back_inserter(results), isMultipleOf2 || isMultipleOf3);

But it does not compile: in C ++, there is no such thing as the || operator for functions.

The easiest way the C ++ standard offers (starting with 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 code compiles and extracts multiples of 2 or 3 from the collection.

But in this way the code acquires an unwanted excess:

lambda syntax: brackets [], parameter list, brackets {...} etc.

parameter: number.

Indeed, we do not need to know about the individual parameters passed to the function object. The purpose of the algorithm is to raise the level of abstraction to the level of the collection. We want the code to express that we extract these types of numbers from collection, and not what we do with single numbers. Even though the result is the same at run time, this is the wrong level of abstraction in the code.

You might think that using lambdas is justified in this case. But in case you are annoyed by the additional code that they force us to write, let’s look at other ways to combine functions with logical operators, such as ||.

I do not claim that these methods are better than lambda, they all have their advantages and disadvantages. In any case, learning is always instructive. And if you have any thoughts on this, I would like to hear them in the comments section.

Decision No. 1: development of the function of the union

I don’t think there is a way to write a statement || for functions in the general case, so that you can write isMultipleOf2 || isMultipleOf3. Indeed, functions in a general sense include lambdas, and lambdas can be of any type. Thus, such an operator will be an operator || for all types. That would be too intrusive for the rest of the code.

If we can not get the operator ||, let’s develop a function to replace it. We can call it something like “or.” We cannot call it “or,” because this name is already reserved in language. We can either put it in a namespace or call it something else.

It would be wise to put such a common name in the namespace in order to avoid collisions. But for example purposes, let’s just call her or_. Intended use or_ will be as follows:

std::copy_if(begin(numbers), end(numbers), back_inserter(results), or_(isMultipleOf2, isMultipleOf3));

How should we realize this? I suggest you try doing it yourself before reading further.



or_ Is a function that takes two and returns one function. We can realize this, returning lambda:

template
auto or_(Function1 function1, Function2 function2)
{
    return [function1, function2](auto const& value){ return function1(value) || function2(value); };
}

We made a choice, accept the lambda parameter by const &. This is due to the fact that in stateless STL algorithms – no stress, and this means that everything is easier when functional objects do not have side effects in STL algorithms, in particular predicates, as we have here.

Solution # 2: operator || for a certain type

Let’s try to return the operator || into the syntax. The problem with the operator || was that we could not implement it for all types.

We can get around this limitation by fixing the type:

template
struct func
{
   explicit func(Function function) : function_(function){}
   Function function_;
};

Then we can define the operator || for this type, and it will not conflict with other types in the code:

template
auto operator||(func function1, Function2 function2)
{
    return [function1, function2](auto const& value){ return function1.function_(value) || function2(value); };
}

The resulting code has the advantage that || there is in its syntax, but a design flaw func:

std::copy_if(begin(numbers), end(numbers), back_inserter(results), func(isMultiple(2)) || isMultiple(3));

We may be able to find a more suitable name for func, but if you have any suggestions, please leave a comment below.

Solution # 3: Using Boost Phoenix

Boost Phoenix’s goal is to write a complex function object with simple code! If you are new to Boost Phoenix, you can check out introduction to boost phoenixto see the code that it allows you to write.

Boost Phoenix, although an impressive library, cannot work wonders and does not compile our initial target code (isMultipleOf2 || isMultipleOf3) But it allows you to use the creation of objects from isMultipleOf2 and isMultipleOf3which will be compatible with the rest of the library.

Boost Phoenix doesn’t use macros at all, but for this particular case:

BOOST_PHOENIX_ADAPT_FUNCTION(bool, IsMultipleOf2, isMultipleOf2, 1)
BOOST_PHOENIX_ADAPT_FUNCTION(bool, IsMultipleOf3, isMultipleOf3, 1)

The first line creates IsMultipleOf2 of isMultipleOf2, and we must indicate that isMultipleOf2 returns bool and takes 1 parameter.

Then we can use them this way (full-code example to show #include):

#include 
#include 
 
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{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    auto results = std::vector{};
 
    using boost::phoenix::arg_names::arg1;
    std::copy_if(begin(numbers), end(numbers), back_inserter(results), IsMultipleOf2(arg1) || IsMultipleOf3(arg1));
}

At the cost of good syntax – use ||this appearance arg1, which means the first argument passed to these functions. In our case, objects successfully passed to this function are elements inside collection numbers.

So what do you think of these methods of combining several functions with logical operations? Do you see other ways to write this with more expressive code?

Free webinar: “STL containers for all occasions”

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *