Standard Library Function Helpers
A comprehensive overview of function helpers in the standard library, including std::invocable, std::predicate and std::function.
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::invocableconcept lets us specify type requirements for any of the callables we've covered - The more specific
std::predicateconcept, lets us specify that our templates require a predicate - The
std::functioncontainer 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
Concepts in C++20
Learn how to use C++20 concepts to constrain template parameters, improve error messages, and enhance code readability.
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 LambdaTo 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 predicateSimilar 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 trueOur 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: 3Below, 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: 6The 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 defeatedStandard 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::lessuses the<operatorstd::less_equaluses the<=operatorstd::equal_touses the==operatorstd::not_equal_touses the!=operatorstd::greateruses the>operatorstd::greater_equaluses 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: 15We 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: 15The full collection of function objects for implementing arithmetic operations are:
std::plususes the binary+operatorstd::minususes the binary-operatorstd::multipliesuses the binary*operatorstd::dividesuses the/operatorstd::modulususes 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:
Bitwise Operators and Bit Flags
Unravel the fundamentals of bitwise operators and bit flags in this practical lesson
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 setThe full collection includes:
std::bit_orwhich uses the|operatorstd::bit_andwhich uses the binary&operatorstd::bit_xorwhich 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::invocableconcept for specifying callable type requirements. - Understanding the
std::predicateconcept 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::functionfor storing callable objects, providing flexibility across different callable types. - The use of class template argument deduction (CTAD) in simplifying
std::functionusage. - Discussed the benefits and usage scenarios of
std::functionversusstd::invocableand when to use each. - Exploration of standard library function objects for implementing comparison, arithmetic, and bitwise operators, reducing the need to write our own.
Pointers to Members
Learn how to create pointers to class functions and data members, and how to use them