One of my pet peeves with teaching FP in C++ is that if we want to have efficient code, we need to catch functions and other callable objects as template arguments.

Because of this, we do not have function signatures that are self-documenting. Consider a function that outputs items that satisfy a predicate to the standard output:

template <typename Predicate>
void write_if(const std::vector<int> &xs, Predicate p) {
                 std::ostream_iterator<int>(std::cout, " "));

We see that the template parameter is named Predicate, so we can imply that it needs to return a bool (or something convertible to bool), and we can deduce from the function name and the type of the first argument that it should be a function that takes an int.

This is a lot of reasoning just to be able to tell what we can pass to the function.

For this reason, Bartosz uses std::function in his blog posts – it tells us exactly which functions we can pass in. But std::function is slow.

So, we either need to have a bad API or a slow API.

With concepts, the things will change.

We will be able to define a really short (and a bit dirty) concept that will check whether the functions we get are of the right signature:

Edit: Changed the concept name to Callable to fit the naming in the standard [func.def] since it supports any callable, not just function objects

template <typename F, typename CallableSignature>
concept bool Callable =
    std::is_convertible<F, std::function<CallableSignature>>::value;

void foo(Callable<int(int)> f)
   // or Callable<auto (int) -> int>
        std::cout << std::invoke(f, 42) << std::endl;

We will be able to call foo with any callable that looks like a int-to-int function. And we will get an error ‘constraint Callable<int(int)> is not satisfied’ for those that do not have the matching signature.

An alternative approach is to use std::is_invocable type trait (thanks Agustín Bergé for writing the original proposal and pointing me to it). It will provide us with a cleaner definition for the concept, though the usage syntax will have to be a bit different if we want to keep the concept definition short and succulent.

template <typename F, typename R, typename ...Args>
concept bool Callable =
    std::is_invocable<R, F, Args...>::value;

void foo(Callable<int, int> f)
    std::cout << std::invoke(f, 42) << std::endl;

When we get concepts (C++20, hopefully), we will have the best of both worlds – we will have an optimal way to accept callable objects as function arguments, and not sacrificing the API to do it.

Book discount

Today, Functional Programming in C++ is again the Deal of the Day – you get half off if you use the code dotd032317au at