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::copy_if(begin(xs),
end(xs),
std::ostream_iterator<int>(std::cout, " "));
p);
}
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 cukic.co/to/manning-dotd