How to pass a polymorphic object to the STL algorithm

Original author: Jonathan Boccara
  • Transfer
As we can read in the first chapter of Effective C ++ , C ++ is essentially a combination of 4 different parts:

  • The procedural part inherited from the language C
  • Object Oriented Part
  • STL trying to follow a functional paradigm
  • Patterns

These four, in essence, sublanguages ​​make up what we call the single C ++ language. Since they are all united in one language, this gives them the opportunity to interact. This interaction sometimes gives rise to interesting situations. Today we will consider one of them - the interaction of an object-oriented model and STL. It can take various forms, and in this article we will consider the transfer of polymorphic functional objects to STL algorithms. These two worlds are not always good in contact, but we can build a fairly good bridge between them.

image

Polymorphic functional objects - what is it?


By a functional object in C ++, I mean an object from which operator () can be called. It can be a lambda function or a functor. Polymorphism can mean different things depending on the programming language and context, but here I will call polymorphic objects of those classes that use inheritance and virtual methods. That is, a polymorphic functional object is something like:

struct Base
{
    int operator()(int) const
    {
        method();
        return 42;
    }
    virtual void method() const { std::cout << "Base class called.\n"; }
};

This functional object does nothing useful, but it’s even good, because the implementation of its methods will not distract us from the main task - to pass its successor to the STL algorithm. And the heir will override the virtual method:

struct Derived : public Base
{
    void method() const override { std::cout << "Derived class called.\n"; }
};

Let's try passing the heir to the STL algorithm in a trivial way, like this:

void f(Base const& base)
{
    std::vector v = {1, 2, 3};
    std::transform(begin(v), end(v), begin(v), base);
}
int main()
{    
    Derived d;
    f(d);
}

What do you think this code will output?

This
Base class called.
Base class called.
Base class called.

Strange, right? We passed an object of the Derived class with an overloaded virtual method to the algorithm, but the algorithm decided to call the base class method instead. To understand what happened, let's take a look at the prototype of the std :: transform function:

template< typename InputIterator, typename OutputIterator, typename Function>
OutputIt transform(InputIterator first, InputIterator last, OutputIterator out, Function f);

Look carefully at its last parameter (Function f) and note that it is passed by value. As explained in Chapter 20 of the same Effective C ++ book, polymorphic objects are “cut off” when we pass them by value: even if the reference to Base const & points to an object of type Derived, creating a copy of base creates an object of type Base, not an object of type Derived.

Thus, we need a way to pass a reference to a polymorphic object, rather than a copy of it, to the STL algorithm.

How to do it?

Let's wrap our object in another


This thought generally comes first: “The problem? Let's solve it by adding indirection! ”If our object must first be passed by reference, and the STL algorithm accepts only objects by value, then we can create an intermediate object that will store a link to the polymorphic object we need, but this one the object can already be passed by value.

The easiest way to do this is to use the lambda function:

std::transform(begin(v), end(v), begin(v), [&base](int n){ return base(n); }

Now the code displays the following: It works, but burdens the code with a lambda function, which, although quite short, is nevertheless written not for the elegance of the code, but only for technical reasons. In addition, in real code, it can look much longer:

Derived class called.
Derived class called.
Derived class called.





std::transform(begin(v), end(v), begin(v), [&base](module::domain::component myObject){ return base(myObject); }

Redundant code using a functional paradigm as a crutch.

Compact solution: use std :: ref


There is another way to pass a polymorphic object by value and it consists in using std :: ref

std::transform(begin(v), end(v), begin(v), std::ref(base));

The effect will be the same as that of the lambda function: Perhaps now you have the question “Why?”. For example, I have it. First, how did this compile at all? std :: ref returns an object of type std :: reference_wrapper that models the link (with the exception that it can be reassigned to another object using operator =). How can std :: reference_wrapper play the role of a functional object? I looked at the documentation for std :: reference_wrapper at cppreference.com and found this:

Derived class called.
Derived class called.
Derived class called.



std :: reference_wrapper :: operator ()

Calls the Callable object, reference to which is stored. This function is available only if the stored reference points to a Callable object.
That is, this is such a special feature in std :: reference_wrapper: if std :: ref accepts a functional object of type F, then the returned object-simulator of the link will also be of a functional type and its operator () will call operator () of type F. Exactly which we needed.

This solution seems to me better than using lambda functions, because the same result is achieved by simpler and more concise code. Perhaps there are other solutions to this problem - I will be glad to see them in the comments.

Also popular now: