Lambdas are a nice recent addition to C++. They are cool, they are hip, and they tend to be overused and misused.

Since lambda expressions came up in C++11 and got a huge boost in usability in C++14, they have been all the rage. Don’t get me wrong. Lambdas really are useful and cool and everything. But reading blog posts, the CppLang Slack channel and other sources lately has given me the impression that some people use lambdas in ways they should not be used.

Lambdas are not a replacement for normal functions

Let’s have a look at this example:

int main() { auto sequence = [](size_t i){ std::vector<size_t> result(i); std::iota(begin(result), end(result), 0); return result; }; auto print = [](auto const& container) { for (auto&& e : container) { std::cout << e << ' '; } std::cout << '

'; }; print(sequence(22)); }

Here, the main function contains the definition of two lambdas that act like normal functions. The actual work done in main is only the single last line, but the function body is blown up to 14 lines total. If a reader wants to know what main does they have to skim past the lambdas, which takes unnecessary time. It can be worse, e.g. if the code of interest is interleaved with lambda definitions:

int main() { auto sequence = [](size_t i){ std::vector<size_t> result(i); std::iota(begin(result), end(result), 0); return result; }; auto s = sequence(22); s.push_back(42); auto print = [](auto const& container) { for (auto&& e : container) { std::cout << e << ' '; } std::cout << '

'; }; print(s); }

Now there is an extra strain on the reader to determine which lines are important to read and which are not. Let’s have a look at a more natural implementation:

auto sequence(size_t i) { std::vector<size_t> result(i); std::iota(begin(result), end(result), 0); return result; } template<class C> auto print(C const& container) { for (auto&& e : container) { std::cout << e << ' '; } std::cout << '

'; } int main() { auto s = sequence(22); s.push_back(42); print(s); }

This is pretty much the same code, besides the little boilerplate needed to declare print and template . The readability has improved vastly, though: main are just three lines that may be enough to know what is going on. If you need to know what sequence does exactly, because it’s poorly named, then you can look into the function as usual.

There might be two little cons to having actual functions instead of lambdas: The names of the functions will be visible to the linker outside the translation unit, i.e. they have external linkage, which also can affect inlining. Secondly, look up rules may differ, which may actually be of concern for something named print . Both issues can, however, be easily remedied by using anonymous namespaces for internal linkage and a named namespace for the look up, if absolutely necessary.

Extra long lambdas

A variant of the above problem is to make necessary lambdas longer than a few lines. Even if you need to use a lambda, e.g. because you have captures and/or you actually need the function object it creates, lambdas should be short, even shorter than your average function.

The reason is that lambdas usually are only one element of a larger context, e.g. an algorithm call. If a single element is larger than the whole rest of its context, the reader will focus on the single element instead of that context. In addition, larger lambda bodies are likely to have a lower level of abstraction than the surrounding function, so the function as a whole violates the SLA principle.

There is nothing that prohibits extracting functions from a lambda body like you would do from a normal function to keep it short and readable.

The functional hype

There are certain people out there that revel in the sweet purity of functional programming. It is very rare to see a conference without at least a few talks about functional C++ these days. People start comparing C++ to Haskell and produce lambdas that return lambdas that generate other lambdas that … you get the gist.

Functional principles are a very interesting topic (I attend or watch those talks myself whenever I come across one), and it is really great to have such facilities in the language. Some of them are even needed to write scalable parallel software. But C++ is not and never will be a functional language, like it is not an object oriented language, either.

Instead, C++ is a multi-paradigm language. It is a box full of different tools, and we do best to use them only where they are appropriate and in the way that makes the best use of them. There is no sense in using a single tool (e.g. lambdas) everywhere we can, in every possible way, only because we finally have it at our disposal.