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:

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 :)

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.