Standard Library Function Helpers

A comprehensive overview of function helpers in the standard library, including std::invocable, std::predicate and std::function.
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, Unlimited Access
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated

In this lesson, we’ll introduce some of the standard library utilities that can make working with the features we covered earlier in this chapter easier. We’ll cover:

  • The std::invocable concept lets us specify type requirements for any of the callables we’ve covered
  • The more specific std::predicate concept, lets us specify that our templates require a predicate
  • The std::function container provides a generalized way to store a callable
  • Some standard library functors allow us to quickly create some commonly needed callables

The std::invocable Concept

In previous sections, when we had a function that receives another function as an argument, we’ve been fairly inelegant with how we specified that type.

We might just set the type to auto, but this eliminates some type safety, and doesn’t document our expectations:

class Party {
public:
  // This is too loose
  bool all_of(auto Pred) {
    return (
      Pred(PlayerOne) &&
      Pred(PlayerTwo) &&
      Pred(PlayerThree)
    );
  }
};

Alternatively, we’ve specified the exact type of argument we expect, such as a function pointer:

class Player {
 public:
  bool isOnline() const { return true; }
};

class Party {
 public:
  using Handler = bool (*)(const Player&);  
  bool all_of(Handler Pred) {          
    return (
      Pred(PlayerOne) &&
      Pred(PlayerTwo) &&
      Pred(PlayerThree)
    );
  }

 private:
  Player PlayerOne;
  Player PlayerTwo;
  Player PlayerThree;
};

struct OnlineChecker {
  bool operator()(const Player& P) {
    return P.isOnline();
  }
};

int main() {
  Party MyParty;
  MyParty.all_of(OnlineChecker{});
}
error: 'bool Party::all_of(Party::Handler)': cannot convert argument 1 from 'OnlineChecker' to 'Party::Handler'

But this is unnecessarily restrictive. Our all_of() function doesn’t care if we provide a function pointer, lambda, or functor. It will be happy to use either of them, so there’s no reason to deny consumers the choice.

As of C++20, the preferred way we specify type requirements is through concepts

The standard library has already provided a concept that can help us here. The std::invocable concept is available after we include <concepts>. To specify that a function template’s argument should be invocable, we would use it like this:

#include <iostream>
#include <concepts>

void Call(std::invocable auto Callback) {
  Callback();
}

void Greet() { std::cout << "Hello From Function\n"; }

struct Greeter {
  void operator()() {
    std::cout << "Hello from Functor\n";
  }
};

int main() {
  Call(Greet);
  Call(Greeter{});
  Call([] { std::cout << "Hello From Lambda"; });
}
Hello From Function
Hello from Functor
Hello From Lambda

To specify the argument list of the required invocable, we would provide the types as template parameters to std::invocable. Below, we require the first argument to Call be an invocable that accepts two integer arguments:

#include <iostream>
#include <concepts>

void Call(std::invocable<int, int> auto Func) {
  Func(1, 2);
}

void Function(int x, int y) {
  std::cout << "Values received by function: "
    << x << ", " << y;
}

struct Functor {
  void operator()(int x, int y) {
    std::cout << "\nValues received by functor: "
      << x << ", " << y;
  }
};

int main() {
  Call(Function);
  Call(Functor{});
  Call([](int x, int y) {
    std::cout << "\nValues received by lambda: "
      << x << ", " << y;
  });
}

Below, we update our original all_of() example to use this concept:

#include <concepts>

class Player {
 public:
  bool isOnline() const { return true; }
};

class Party {
 public:
  bool all_of(
    std::invocable<const Player&> auto Pred) {  
    return (
      P(PlayerOne) &&
      P(PlayerTwo) &&
      P(PlayerThree)
    );
  }

 private:
  Player PlayerOne;
  Player PlayerTwo;
  Player PlayerThree;
};

struct OnlineChecker {
  bool operator()(const Player& P) {
    return P.isOnline();
  }
};

int main() {
  Party MyParty;
  MyParty.all_of(OnlineChecker{});
}

The std::predicate Concept

When our function is expected to return a bool, or something convertible to a bool, the standard library includes the std::predicate concept to help us out:

#include <iostream>
#include <concepts>

void Call(std::predicate auto Func) {
  std::cout << "That was a predicate";
}

void Call(auto Func) {
  std::cout << "\nThat was not a predicate";
}

struct SomeType {};

int main() {
  Call([] { return true; });
  Call([] { return SomeType{}; });
}
That was a predicate
That was not a predicate

Similar to std::invocable, we can provide the required argument list as template parameters. Below, we create a template where an argument is required to be a predicate that accepts an int and a float:

#include <concepts>
#include <iostream>

template <std::predicate<int, float> T>
void LogIfTrue(T Predicate, int x, float y) {
  if (Predicate(x, y)) {
    std::cout << "Predicate returned true";
  }
}

int main() {
  LogIfTrue([](int x, float y) {
    return x == y;
  }, 5, 5.0f);
}
Predicate returned true

Our earlier all_of() function was expecting the callback to return a boolean, so we’d likely prefer to use std::predicate in this scenario, rather than the less restrictive std::invocable:

#include <concepts>

struct Player {};
class Party {
public:
  template <std::predicate<Player&> T>
  bool all_of(T Predicate){
    return (
      Predicate(PlayerOne) &&
      Predicate(PlayerTwo) &&
      Predicate(PlayerThree)
    );
  }
};

Concepts for Other Return Types

The standard library currently doesn’t provide a concept that requires any return type other than boolean, using std::predicate. If we need this, we can define our own. Below, we create a concept for a function that accepts two int objects and returns an int:

#include <iostream>
#include <concepts>

template <typename T>
concept TwoIntsToInt = requires(T Callable) {
  { Callable(1, 1) } -> std::same_as<int>;
};

void Call(TwoIntsToInt auto Func, int x, int y) {
  std::cout << "The callback returned: "
    << Func(x, y);
}

int main() {
  Call([](int x, int y) {
    return x + y;
  }, 1, 2);
}
The callback returned: 3

Below, we create a template that requires a callable return an int, and for the argument list to be provided as template parameters. This effectively replicates the behavior of std::predicate except, where std::predicate requires the function return a bool, our IntReturner concept requires an int be returned.

Note: The ... syntax in the following example is a parameter pack, which is how we represent a variable number of parameters. We cover this in more detail later in this chapter in our lesson on variadic functions.

#include <iostream>
#include <concepts>

template<typename T, typename... Args>
concept IntReturner =
  requires(T Func, Args... args) {
    { Func(args...) } -> std::same_as<int>;
};

template <IntReturner<int, float, double> T>
void Call(T Func, int x, float y, double z) {
  std::cout << "The callback returned: "
    << Func(x, y, z);
}

int main() {
  Call([](int x, float y, double z) -> int {
    return x + y + z;
  }, 1, 2.0f, 3.0);
}
The callback returned: 6

The std::function Container

The std::function container within the <functional> header gives us a generalized and flexible way to represent callables such as lambdas, functors, or function pointers. If we had a callable that returns void and has no arguments, we could store it as a variable called Example using the following syntax:

#include <functional>

std::function<void()> Example;

Below, we create a container for storing a callable that receives two int arguments, and returns a bool:

#include <functional>

std::function<bool(int, int)> Example;

Class Template Argument Deduction

As with most containers, when we’re providing an initial value to our std::function, we do not need to provide the template arguments. The compiler can automatically infer the type required, based on the type of object we’re initializing the container with.

This technique is called class template argument deduction, or CTAD:

#include <functional>

bool Function(int x, int y) { return true; };

int main() {
  std::function Callable{Function};
}

Why do we need std::function?

The main benefit of std::function is similar to the benefit of std::invocable - that is, it does not prescribe the specific type of callable, thereby giving us more flexibility. Below, we create a std::function that first stores a function pointer, then a functor, then a lambda:

#include <functional>

void Function(){};

struct Functor{
  void operator()(){}
};

int main() {
  std::function Callable{Function};
  Callable = Functor{};
  Callable = [] {};
}

Empty std::function containers

A std::function container can be empty. We can check for this by coercing it to a boolean:

#include <iostream>
#include <functional>

int main() {
  std::function<void()> Callable;

  if (!Callable) {
    std::cout << "Callable is empty";
  }

  Callable = [] {};

  if (Callable) {
    std::cout << "...but not any more!";
  }
}
Callable is empty...but not any more!

We can assign nullptr to a std::function, effectively setting it to the empty state:

#include <functional>
#include <iostream>

int main() {
  std::function Callable{[] {}};

  if (Callable) {
    std::cout << "A function is stored";
  }

  Callable = nullptr;

  if (!Callable) {
    std::cout << "...but not any more!";
  }
}
A function is stored...but not any more!

Using std::function as a member variable

The main place we’ll see std::function being used is within class definitions. Below, we create a Player class that allows the outside world to provide a callback to be invoked when our Player is defeated, which it stores in a std::function.

#include <functional>
#include <iostream>

class Player{
 public:
   Player(std::invocable auto DefeatCallback)
     : OnDefeat { DefeatCallback } {}

  void TakeDamage() {
    Health -= 50;
    if (Health <= 0 && OnDefeat) {
      OnDefeat();
    }
  }

 private:
  int Health{100};
  std::function<void()> OnDefeat;
};

int main() {
  Player PlayerOne {[] {
    std::cout << "The player was defeated";
  }};

  PlayerOne.TakeDamage();
  PlayerOne.TakeDamage();
}
The player was defeated

Observers

The previous example shows the basis of a powerful design pattern called observers. Our Player object supports observers, as it gives other code the option to subscribe to specific events. This is done by the other code providing a callable to Player, and the Player class then calling it when the event happens.

This allows those objects to react to those events, even if they were not responsible for causing them. Our previous simple example only allows one observer, but this can be expanded.

For example, the Player class could store an array of std::function objects, and provide methods such as Subscribe() and Unsubscribe() to add and remove functions to that collection. Then, when the event happens, we could iterate over that array and invoke all the callbacks, thereby notifying every observer.

FAQ: std::function vs std::invocable

A common question relates to the difference between std::function and std::invocable. Their use cases seem similar, and the decision mostly comes up when defining function parameter types:

void A(std::function<void(int, int)> F) {}
void B(std::invocable<int, int> auto F) {}

The key difference is that std::function is for storing callables, whilst std::invocable is for checking if something is callable. std::invocable, like any concept, is designed for analyzing types, runs at compile time, and has no run-time performance cost.

std::function can provide an almost similar level of type safety at compile time but, at run time, it also creates an object to wrap the function. When our callable is a simple function argument, for example, creating this intermediate object is rarely useful.

So, if we’re just using std::function for type safety and documentation, we should consider using concepts such as std::invocable or std::predicate instead.

Standard Library Function Objects

When writing functional code, we’ll commonly need to create very simple callables that accept two arguments and apply some basic operator on them. The standard library contains a collection of types that can help us create functors that implement these operators.

Everything we cover in this section is within the <functional> header:

#include <functional>

Comparison Operators

A common requirement we’ll have when sorting a container, for example, is to provide a function specifying how we want the container to be sorted. Typically, these functions will accept two arguments and should return true if the first argument comes before the second in our sort order.

Below, we sort a forward list in ascending order, by providing a lambda that accepts two arguments, x and y, and returns the result of x < y:

#include <iostream>
#include <forward_list>

int main() {
  std::forward_list Nums{3, 1, 2, 5, 4};

  Nums.sort([](int x, int y) { return x < y; });

  std::cout << "Sorted Container: ";
  for (auto Num : Nums) {
    std::cout << Num << ", ";
  }
}
Sorted Container: 1, 2, 3, 4, 5,

The standard library can help us here - a functor that accepts two arguments and returns the result of using the < operator with them is available by instantiating std::less. As such, we didn’t need to write the lambda - we can just do this:

#include <forward_list>
#include <iostream>
#include <functional>

int main() {
  std::forward_list Nums{3, 1, 2, 5, 4};

  Nums.sort(std::less{});

  std::cout << "Sorted Container: ";
  for (auto Num : Nums) {
    std::cout << Num << ", ";
  }
}
Sorted Container: 1, 2, 3, 4, 5,

A full suite of types for creating functors that accept two arguments and return the result of a comparison operator is available:

  • std::less uses the < operator
  • std::less_equal uses the <= operator
  • std::equal_to uses the == operator
  • std::not_equal_to uses the != operator
  • std::greater uses the > operator
  • std::greater_equal uses the >= operator

Arithmetic Operators

An algorithm like fold_left_first() accepts a collection of objects, and returns a single object. The single object is generated by folding the collection together - that is, combining two objects at a time until only one object remains.

We provide a function to specify how objects are combined. Below, we provide a lambda that adds the objects together. The net result of this is that the algorithm returns the sum of every number in our collection:

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

int main() {
  std::vector Nums{1, 2, 3, 4, 5};

  auto Sum{std::ranges::fold_left_first(
    Nums, [](int x, int y) { return x + y; })};

  std::cout << "Sum: " << Sum.value_or(0);
}
Sum: 15

We cover fold algorithms in more detail later in the course. For now, the key point is that the standard library provides types for creating functors that implement basic arithmetic operators like +. As such, we could replace the lambda in our previous example with an instance of std::plus:

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

int main() {
  std::vector Nums{1, 2, 3, 4, 5};

  auto Sum{std::ranges::fold_left_first(
    Nums, std::plus{})};

  std::cout << "Sum: " << Sum.value_or(0);
}
Sum: 15

The full collection of function objects for implementing arithmetic operations are:

  • std::plus uses the binary + operator
  • std::minus uses the binary - operator
  • std::multiplies uses the binary * operator
  • std::divides uses the / operator
  • std::modulus uses the % operator

We also have std::negate, which uses the unary - operator. For example, a functor created from std::negate will accept a single argument x and will return -x.

Bitwise Operators

Less commonly, the standard library includes functors that implement the bitwise operators. We covered these operators in more detail in the previous course:

Below, we use std::bit_or to create a functor that implements the bitwise or operator |:

#include <vector>
#include <iostream>
#include <algorithm>
#include <functional>
#include <bitset>

std::bitset<3> FLAG_ONE   {1 << 0};
std::bitset<3> FLAG_TWO   {1 << 1};
std::bitset<3> FLAG_THREE {1 << 2};

int main() {
  std::vector Bits{FLAG_ONE, FLAG_THREE};

  std::bitset Bitset{
    std::ranges::fold_left_first(
      Bits, std::bit_or{}
    ).value_or(0)};

  std::cout << "FLAG_ONE "
    << (Bitset[0] ? "is" : "is not")
    << " set\n";

  std::cout << "FLAG_TWO "
    << (Bitset[1] ? "is" : "is not")
    << " set\n";

  std::cout << "FLAG_THREE "
    << (Bitset[2] ? "is" : "is not")
    << " set\n";
}
FLAG_ONE is set
FLAG_TWO is not set
FLAG_THREE is set

The full collection includes:

  • std::bit_or which uses the | operator
  • std::bit_and which uses the binary & operator
  • std::bit_xor which uses the ^ operator

We also have std::bit_not, which uses the unary ~ operator. For example, a functor created from std::bit_not will accept a single argument x and will return ~x.

Summary

In this lesson, we've explored function helpers available in the standard library. This included concepts like std::invocable and std::predicate, the std::function container, and the suite of standard library functors for implementing basic operations.

Key Takeaways

  • Introduction to the std::invocable concept for specifying callable type requirements.
  • Understanding the std::predicate concept for functions expected to return a boolean.
  • Demonstrated how to create custom concepts for functions with specific return types beyond boolean.
  • How to use std::function for storing callable objects, providing flexibility across different callable types.
  • The use of class template argument deduction (CTAD) in simplifying std::function usage.
  • Discussed the benefits and usage scenarios of std::function versus std::invocable and when to use each.
  • Exploration of standard library function objects for implementing comparison, arithmetic, and bitwise operators, reducing the need to write our own.

Was this lesson useful?

Next Lesson

Pointers to Members

Learn how to create pointers to class functions and data members, and how to use them
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated
A computer programmer
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, Unlimited Access
Working with Functions
Next Lesson

Pointers to Members

Learn how to create pointers to class functions and data members, and how to use them
Abstract art representing computer programming
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved