C++17 In Detail

07 August 2020

C++ Lambda Week: Some Tricks

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:

+[]{}

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 print utility that outputs the variadic argument list:

#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.

See More in Lambda Story

If you like to know more, you can see my book on Lambdas! Here are the options on how to get it and join 1000+ of readers:

If you like my work and you want to get extra C++ content, exlusive articles and weekly curated news, then check out my Patreon website:

© 2017, Bartlomiej Filipek, Blogger platform
Disclaimer: Any opinions expressed herein are in no way representative of those of my employers. All data and information provided on this site is for informational purposes only. I try to write complete and accurate articles, but the web-site will not be liable for any errors, omissions, or delays in this information or any losses, injuries, or damages arising from its display or use.
This site contains ads or referral links, which provide me with a commission. Thank you for your understanding.