There are two problems that often appear in real world and can be solved with the same tool.
Let’s say we have a function that takes…
- A variable of a very long typename, that has to be written by hand or using a typedef.
1 2
using TBazArray = ::google::protobuf::RepeatedPtrField<Namespace::Foo::Bar::Baz>; bool AllShallFall(const TBazArray& bazArray) { ... };
Advice: just use a template, then you won’t be searching over the repository to find out what type exactly you are supposed to use.
1 2
template<typename T> bool AllShallFall(const T& bazArray) { ... };
- A function. A common pattern is that the caller sends a lambda, the callee receives a
std::function
(you don’t usually use a “function pointer” in such cases, right?) Thestd::function
has a flaw of allocating memory on the heap, and is not necessary at all.1 2 3
bool AllShallFall(std::function<int(void)> callback) { ... }; // ... AllShallFall([]() { return 4; });
Advice: you can get rid of the unnecessary layer and pass the lambda “as is”. The compiler is able to inline and optimize the code.
1 2 3
template<typename T> AllShallFall(T callback) { ... }; // ... AllShallFall([]() { return 4; });
One has to be careful about copying big objects. A lambda is a closure type object, which size in bytes depends on size of the captured data (references/pointers add 8 bytes, objects taken by value add their
sizeof
). The example above always copies the closure type object, but it makes no difference given that it has zero size (the lambda captures nothing).
The code above is not usable when there is a capture of a non-copyable object:
1
2
3
std::unique_ptr i = std::make_unique<int>(3);
auto l = [i = std::move(i)]() { return 4; };
AllShallFall(l);
Therefore, to optimize passing a lambda in any circumstances, it’s better to use universal references:
1
template<typename T> AllShallFall(T&& callback) { ... };