Function Objects (STL)

Introduction

Everybody programming in C++ using the standard template library (STL) has come across some kind of strange-looking thing named function object or functor. Although they are regular C++ objects just like any other, they are mostly related to the standard template library (STL) and indeed these objects are heavily used in conjunction with it. To be more precise, they are mostly being used together with the algorithms of the STL.

As with many other parts of the STL, this concept is pretty complex and thus—although we are trying to explain things in a simple way—we have to expect a little bit of knowledge about the basic principles of programming as well as some knowledge of the STL, escpecially about its algorithms, from you. Furthermore, by no means is this supposed to be a complete tutorial to function objects.

Basics of Function Objects

If you separate the two words function and object, you already know each one on its own. If you are already a programmer—which we assume at this point—you know the meaning of each one in regard to software engineering:

  • A function is the typical way of getting a particular job done in C++ programs; in other words, it defines the execution flow of a specific operation. It usually has one defined entry and one defined exit point. There have been many debates on whether there should only be one or several exit point(s); however, this does not matter in regard to this article.

  • An object refers to the concrete creation of a datatype that takes up a certain amount of space at a specific memory location. The term object is interchangeable with the term instance which, in this regard, means the same.

A function object extends the characteristics of regular functions by using object-oriented C++ features such as generic prgoramming and abstraction. Thus, they can be referred to as smart functions that provide several advantages over regular functions:

  • Function objects can have member functions as well as member attributes. Thus, they can carry a state that even can be different at the same time.

  • Due to their nature, function objects can be initialized before their usage.

  • Opposed to regular functions that can only have different types if their signatures differs, function objects can have different types even with the same signature; this ensures the type safety of the C++ language. If you provide a function object as the sorting criteria for a collection, it is guaranteed that you cannot assign, combine, or compare collections with a different sorting criteria.

  • Function objects are usually faster than regular functions.

Creation of Function Objects

Function objects are often referred to as smart functions due to their advantages pointed out in the previous paragraph. That also means that functions and function objects are closely related and indeed…any object that behaves like a function can be both seen and used as one.

So, what is the magical part? Think about what a function call comes down to…

void func(int);

int main()
{
   func(3);
   return 0;
  }

By looking at this (very simple) example, you actually can divide a function call into three pieces:

  • You call a function by name. (To simplify matters, we don’t deal with function pointers, RPC calls, and the like here.)

  • You use parentheses.

  • You can (optionally) pass arguments.

To create an object that behaves just like a function, you only need to provide a way to call this object by name using parentheses and (optionally) passing arguments. The solution to the problem is the so-called function call operator that is just another ordinary operator that can be defined. The operator name is simply a pair of parentheses: ‘()’.

As all of the other available operators, it follows the general layout scheme:

class foo
{
   public:
   // ...
    return_type operator operator_name (argument_list);
   // ...
};

Thus, to create a function object, you simply need to provide a function call operator…

class function_object
{
   public:
      // ...
      int operator()(int i) { return i; }
      // ...
};

That is basically all…this class now can be called just like any regular function:

#include <iostream>

int main()
{
   function_object fo;
   // calls operator '()' of class 'function_object'
   std::cout << fo(5) << std::endl;
   // calls operator '()' of class 'function_object'
   std::cout << fo.operator()(2) << std::endl;
   return 0;
}

Classification of Function Objects

Function objects can be classified by more than one criterion, but the most important one is whether or not they carry a state. Three types of function objects can be differentiated:

Stateless

Stateless function objects are the closest correspondent to a regular function. The function object doesn’t have data members, or, if it does, they have no impact whatsoever on the function call operator (that is, are not used at all by this operator, either directly nor indirectly):

#include <iostream>

class stateless_function_object
{
public:
   int operator()(int i) const { return i * i; }
};

int main()
{
   stateless_function_object fo;
   std::cout << fo(2) << std::endl;    // will output 4
   return 0;
}

Note: If ‘stateless_function_object‘ had any data members, this had been almost a certain sign of poor design: one class, one responsibility is what one should aim for.

Stateful (Invariable)

Invariable stateful function objects do have a state, but the function call operator is declared constant. Semantically, this means that the operation will use the state, but won’t change it:

#include <iostream>

class invariant_function_object
{
public:
   invariant_function_object(int state): state_(state) {}
   int operator()(int i) const { return i * state_; }

private:
   int state_;
};

int main()
{
   invariant_function_object fo(3);
   std::cout << fo(2) << std::endl;    // will output 6
   return 0;
}

Make no mistake here: The first line in ‘main()‘ creates the function object, calling its constructor, and setting the internal state to 3. The second line calls the function call operator.

As you see, the function call operator uses the internal state, but doesn’t change it. Any result in changing the invariant will result in a compiler error because the function call operator is not allowed to modify any member attributes (this isn’t true for ‘mutable‘ member attributes, though).

Stateful (Variable)

Variable stateful functions objects not only have a state, but also can change this state with each operation.

#include <iostream>
#include <vector>
#include <algorithm>

class stateful_function_object
{
public:
   stateful_function_object(): state_(0) {}
   int operator()(int i) { return state_ += i; }
   int get() const { return state_; }

private:
   int state_;
};

int main()
{
   std::vector<int> vec;
   vec.push_back(1);
   vec.push_back(2);
   vec.push_back(3);
   vec.push_back(4);
   vec.push_back(5);

   stateful_function_object fo;
   fo = std::for_each(vec.begin(), vec.end(), fo);

   // will output 15 (1 + 2 + 3 + 4 + 5)
   std::cout << fo.get() << std::endl;

   return 0;
}

There is quite a lot going on here…so, take a closer look:

  • The general idea of this function object is to sum all the elements in the collection which in this example consists of the numbers 1 to 5. Summation is being done by passing the collection to the STL algorithm ‘for_each‘. This algorithm takes the beginning and the end of a collection and either a function object or a regular function that is being called for every element in the specified range of the collection.

    The standard does not guarantee in which order the algorithm passes the elements to the function object. The only guarantee it gives is that the algorithm will pass each element once (and only once) to the specified function object (or regular function). When summing up elements, it does not matter. Thus, a stateful function object should never rely on any order in such a case.

  • To be able to get the result (in the example, the sum of all elements), the algorithm ‘for_each‘ returns a copy of the internally modified function object. This is important because the algorithm internally uses a temporary copy of the function object being passed (because it is passed by value). When working with stateful function objects, always keep the copy semantics of them in mind.

More by Author

Must Read