Last Update:
Predefined C++20 Concepts: Callables
Table of Contents
Before you start implementing your custom concepts, it’s good to review some goodies in the Standard Library. There’s a high chance that there’s already a predefined concept for you.
Today let’s have a look at concepts related to callable objects.
Where to find them
You can find most of the predefined concepts in the <concepts>
header.
Here’s a good list available at cppreference - Concepts library
What’s more, you can also have a look at section 18 from the C++ Specification: https://eel.is/c++draft/#concepts
Additional concepts can be found in:
- iterators library - the
<iterator>
header. - the the algorithms library - also in the
<iterator>
header. - the ranges library - in the
<ranges>
header. - And additional concept for the
<random>
header -uniform_random_bit_generator
.
Callable concepts
In this category we have six concepts:
invocable
/regular_invocable
predicate
relation
equivalence_relation
strict_weak_order
They build the following hierarchy:
Read on to see the core concept in the hierarchy: std::invocable
:
The std::invocable
concept
In short, the std::invocable
concept means “can it be called with `std::invoke”.
template< class F, class... Args >
concept invocable =
requires(F&& f, Args&&... args) {
std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
};
From its definition, we can see that it uses a requires
expression to check if a given function object and a list of arguments can be called with std::invoke
.
Sidenote: You can read more about
std::invoke
in my separate article: C++20 Ranges, Projections, std::invoke and if constexpr - C++ Stories or this one: 17 Smaller but Handy C++17 Features - C++ Stories.
Some examples:
#include <concepts>
#include <functional>
#include <iostream>
template <typename F>
requires std::invocable<F&, int>
void PrintVec(const std::vector<int>& vec, F fn) {
for (auto &elem : vec)
std::cout << fn(elem) << '\n';
}
int main() {
std::vector ints { 1, 2, 3, 4, 5};
PrintVec(ints, [](int v) { return -v; });
}
We can also make it shorter with abbreviated function templates:
void f2(C1 auto var); // same as template<C1 T> void f2(T), if C1 is a concept
In our example this translates into:
void PrintVec(const std::vector<int>& vec, std::invocable<int> auto fn) {
for (auto &elem : vec)
std::cout << fn(elem) << '\n';
}
Here’s the main part:
std::invocable<int> auto fn
Error Messages
Now, let’s try to violate a concept with:
PrintVec(ints, [](int v, int x) { return -v; });
So rather than a single int
argument, my lambda requires two parameters. I got the following error on GCC:
<source>:7:6: note: template argument deduction/substitution failed:
<source>:7:6: note: constraints not satisfied
In file included from <source>:1:
/opt/compiler-explorer/gcc-trunk-20210513/include/c++/12.0.0/concepts: In substitution of 'template<class F> requires invocable<F&, int> void PrintVec(const std::vector<int>&, F) [with F = main()::<lambda(int, int)>]':
It’s pretty clear that we don’t have a match in requirements.
But, on the other hand compilers also did well even before concepts:
<source>:16:13: required from here
<source>:9:24: error: no match for call to '(main()::<lambda(int, int)>) (const int&)'
9 | std::cout << fn(elem) << '\n';
| ~~^~~~~~
<source>:9:24: note: candidate: 'int (*)(int, int)' (conversion)
But please note that it’s only for simple functions. If you have long chains of function templates, lots of instantiations, it’s more beneficial to get constraint errors as early as possible.
You can play with code @Compiler Explorer
What’s all about this regularity
?
What’s the difference between invocable
and regular_invocable
?
There’s already an answer on that :)
- c++ - What is the difference between std::invocable and std::regular_invocable concepts? - Stack Overflow
- Or in the C++ Specification: https://eel.is/c++draft/concepts.equality
In short, regularity tells us the following:
An expression is equality preserving if it results in equal outputs given equal inputs.
It looks like it’s purely semantic information for now, and they are syntactically the same. The compiler cannot check it on compile time.
For example:
#include <concepts>
int main() {
auto fn = [i=0](int a) mutable { return a + ++i; };
static_assert(std::invocable<decltype(fn), int>);
static_assert(std::regular_invocable<decltype(fn), int>);
return 0;
}
See the example @Compiler Explorer
In the above example fn
is not regular, because it contains a state that affects the return value. Each time you call fn()
then you’ll get a different value:
fn(10) != fn(10);
However, when you compile the code, both of static_assert
checks yield the same result.
Writing regular_invocable
is a better practice, though, as it conveys more information in the API.
Thanks to Barry Revzin and Ólafur Waage for a Twitter discussion on that :)
predicate
After discussing the core concept, we can move to its first derivative:
https://eel.is/c++draft/concept.predicate
template<class F, class... Args>
concept predicate =
regular_invocable<F, Args...> &&
boolean-testable<invoke_result_t<F, Args...>>;
In short, this is a callable that returns a value convertible to bool
. The boolean-testable
check is no a real concept; it’s an exposition-only concept.
Please notice that the predicate
uses regular_invocable
, so the interface is “stronger” than when using invocable
.
An example:
#include <concepts>
#include <functional>
#include <iostream>
void PrintVecIf(const std::vector<int>& vec, std::predicate<int> auto fn) {
for (auto &elem : vec)
if (fn(elem))
std::cout << elem << '\n';
}
int main() {
std::vector ints { 1, 2, 3, 4, 5};
PrintVecIf(ints, [](int v) { return v % 2 == 0; });
}
This looks very cool and is so expressive!
Thanks to concepts the function declaration conveys more information about the callable. It’s better than just:
template <typename Fn>
void PrintVecIf(const std::vector<int>& vec, Fn fn);
With std::predicate<int>
we can clearly see what the function expects: a callable that takes one int and returns something convertible to bool.
relation
This one is a bit more complicated. Here’s the definition:
template<class R, class T, class U>
concept relation =
predicate<R, T, T> && predicate<R, U, U> &&
predicate<R, T, U> && predicate<R, U, T>;
https://eel.is/c++draft/concept.relation
To understand it better, let’s see some unit tests that we can grab from this repository - libstdc++-v3 test suite:
static_assert( ! std::relation<bool, void, void> );
static_assert( ! std::relation<bool(), void, void> );
static_assert( ! std::relation<bool(), int, int> );
static_assert( std::relation<bool(*)(int, int), short, long> );
static_assert( std::relation<bool(&)(const void*, const void*), char[2], int*> );
Now, we have two additional concepts which are exactly the same as std::relation
, but they mean some slightly different categories:
template < class R, class T, class U >
concept equivalence_relation = std::relation<R, T, U>;
Semantically equivalence
means a relation that is reflexive, symmetric, and transitive.
And another one:
template < class R, class T, class U >
concept strict_weak_order = std::relation<R, T, U>;
This time, in short, as I found on this old page:
A Strict Weak Ordering is a Binary Predicate that compares two objects, returning true if the first precedes the second.
Summary
Along with the language support for Concepts, C++20 also offers a large set of predefined concepts. In most cases, they are formed out of existing type traits, but there are many new named requirements.
The exciting part is that you can learn a lot about the overall design and granularity of requirements by exploring those Standard Library concepts.
In this blog post, we reviewed concepts for callables. The main one is invocable
, and then we have std::predicate
and std::relation
.
From my perspective, the two concepts (or three): std::inocable
, std::regular_invocable
and std::predicate
can increase readability and expressiveness in my projects. I’m still looking for some other examples with std::relation
. Please help if you have such use cases.
Back to you
- Have you started using concepts?
- What predefined concepts have you used so far?
Let us know in the comments below the article.
I've prepared a valuable bonus for you!
Learn all major features of recent C++ Standards on my Reference Cards!
Check it out here: