We’re on the last day of the lambda week. We have all the essential knowledge, and now we can learn some tricks!
The Series
This blog post is a part of the series on lambdas:
- The syntax changes (Tuesday 4th August)
- Capturing things (Wednesday 5th August)
- Going generic (Thursday 6th August)
- Tricks (Friday 5th August)(this post)
+[]{}
Have a closer look:
#include <type_traits> int main() { auto funcPtr = +[]{}; static_assert(std::is_same<decltype(funcPtr), void (*)()>::value); }
Please notice the strange syntax with
+. If you remove the plus sign, then the
static_assert fails. Why is that?
To understand how it works we can look at the output generated by the C++ Insights project. See the working example:
using FuncPtr_4 = void (*)(); FuncPtr_4 funcPtr = +static_cast<void (*)()>(__la.operator __la::retType_4_18()); /* PASSED: static_assert(std::integral_constant<bool, 1>::value); */ // __la is __lambda_4_18 in cppinights
The code uses
+ which is a unary operator. This operator can work on pointers, so the compiler converts our stateless lambda into a function pointer and then assigns it to
funcPtr. On the other hand, if you remove the plus, then
funcPtr is just a regular closure object, and that’s why the
static_assert fails.
While it’s probably not the best idea to write such a syntax with “+”, it has the same effect if you write
static_cast. One of the applications of this technique is a situation when you don’t want the compiler to create too many function instantiations.
Have you used this pattern? Maybe thare are some other use cases?
IIFE -
[](){}();
In most of the examples in the lambda series, you could notice that I defined a lambda and then call it later.
However, you can also invoke lambda immediately:
#include <iostream> int main() { int x = 1, y = 1; [&]() noexcept { ++x; ++y; }(); // <-- call () std::cout << x << ", " << y; }
As you can see above, the lambda is created and isn’t assigned to any closure object. But then it’s called with
(). If you run the program, you can expect to see
2, 2 as the output.
This kind of expression is called "Immediately Invoked Functional Expression" and might be useful when you have a complex initialisation of a
const object.
const auto val = []() { /* several lines of code... */ }(); // call it!
Above,
val is a constant value of a type returned by lambda expression, i.e.:
// val1 is int const auto val1 = []() { return 10; }(); // val2 is std::string const auto val2 = []() -> std::string { return "ABC"; }();
You can see more in my separate article on that topic: Bartek’s coding blog: C++ Tricks: IIFE for Complex Variable Initialization.
Variadic Generic Lambdas and Fold Expression
Thanks to fold expressions in C++17 we can write even more compact code! For example we can write a simple
#include <iostream> int main() { const auto printer = [] (auto... args) { (std::cout << ... << args) << '\n'; }; printer(1, 2, 3, "hello", 10.5f); }
However, if you run the code it will print all arguments without any separator:
123hello10.5
To solve this issue, we can introduce a little helper and also fold over the comma operator rather than over
<<:
#include <iostream> int main() { const auto printer = [] (auto... args) { const auto printElem = [](auto elem) { std::cout << elem << ", "; }; (printElem(args), ...); std::cout << '\n'; }; printer(1, 2, 3, "hello", 10.5f); }
And now we have the following output:
1, 2, 3, hello, 10.5,
This can be even shortened into:
const auto printer = [] (auto... args) { ((std::cout << args << ", "), ...); std::cout << '\n'; };
And if we do not want to show the last comma at the end of the print sequence we can do the following:
#include <iostream> int main() { const auto printer = [] (auto first, auto... args) { std::cout << first; ((std::cout << ", " << args), ...); std::cout << '\n'; }; printer(1, 2, 3, "hello", 10.5f); }
This time we need to use a generic template argument for the first entry and then a variadic parameter list for the rest. We can then print the first element and then add a comma before other entries. The code will now print:
1, 2, 3, hello, 10.5
Some More Interesting Cases
Some time ago I wrote a separate article about other aspects of lambdas, have a look: Bartek’s coding blog: 5 Curious C++ Lambda Examples: Recursion, constexpr, Containers and More.
Summary
Thanks for reading the whole series on Lambdas! We covered basic things, but I’m sure you can expand from this point easily.
- What’s your favourite “feature” of lambdas?
- What are your best use cases?
Let us know in comments below the article.)