More Powerful Lambdas with C++20


Thanks to C++20, lambdas become more powerful. From the various lambda improvements, template parameters for lambdas are my favorite ones.

 TimelineCpp20CoreLanguageLambdas support with C++20 template parameters, can be default-constructed and support copy-assignment, when they have no state, and can be used in unevaluated contexts. Additionally, they detect when you implicitly copy the this pointer. This means a significant cause of undefined-behavior with lambdas is gone.

Let's start with template parameters for lambdas.

Template Parameter for Lambdas

Admittedly, the differences between typed lambdas, generic lambdas, and template lambdas (template parameter for lambdas) are subtle.

Four lambda variations

The following program presents four variations of the add function using lambdas for their implementation.


// templateLambda.cpp

#include <iostream>
#include <string>
#include <vector>

auto sumInt = [](int fir, int sec) { return fir + sec; };            // only to int convertible types    (C++11)
auto sumGen = [](auto fir, auto sec) { return fir + sec; };          // arbitrary types                  (C++14)
auto sumDec = [](auto fir, decltype(fir) sec) { return fir + sec; }; // arbitrary, but convertible types (C++14)
auto sumTem = []<typename T>(T fir, T sec) { return fir + sec; };    // arbitrary, but identical types   (C++20)

int main() {
    std::cout << std::endl;
                                                                            // (1)
    std::cout << "sumInt(2000, 11): " << sumInt(2000, 11) << std::endl;  
    std::cout << "sumGen(2000, 11): " << sumGen(2000, 11) << std::endl;
    std::cout << "sumDec(2000, 11): " << sumDec(2000, 11) << std::endl;
    std::cout << "sumTem(2000, 11): " << sumTem(2000, 11) << std::endl;
    std::cout << std::endl;
                                                                            // (2)
    std::string hello = "Hello ";
    std::string world = "world"; 
    // std::cout << "sumInt(hello, world): " << sumInt(hello, world) << std::endl; ERROR
    std::cout << "sumGen(hello, world): " << sumGen(hello, world) << std::endl;
    std::cout << "sumDec(hello, world): " << sumDec(hello, world) << std::endl;
    std::cout << "sumTem(hello, world): " << sumTem(hello, world) << std::endl;
    std::cout << std::endl;
                                                                             // (3)
    std::cout << "sumInt(true, 2010): " << sumInt(true, 2010) << std::endl;
    std::cout << "sumGen(true, 2010): " << sumGen(true, 2010) << std::endl;
    std::cout << "sumDec(true, 2010): " << sumDec(true, 2010) << std::endl;  
    // std::cout << "sumTem(true, 2010): " << sumTem(true, 2010) << std::endl; ERROR
    std::cout << std::endl;

Before I show the presumably astonishing output of the program, I want to compare the four lambdas.

  • sumInt
    • C++11
    • typed lambda
    • accepts only to int convertible type
  • sumGen
    • C++14
    • generic lambda
    • accepts all types
  • sumDec
    • C++14
    • generic lambda
    • the second type must be convertible to the first type
  • sumTem
    • C++20
    • template lambda
    • the first type and the second type must be the same

What does this mean for template arguments with different types? Of course, each lambda accepts int's (1), and the typed lambda sumInt does not accept strings (2).

Invoking the lambdas with the bool true and the int 2010 may be surprising (3).

  • sumInt returns 2011 because true is integral promoted to int.
  • sumGen returns 2011 because true is integral promoted to int. There is a subtle difference between sumInt and sumGen, which I present in a few lines.
  • sumDec returns 2. Why? The type of the second parameter sec becomes the type of the first parameter fir: thanks to (decltype(fir) sec), the compiler deduces the type of fir and makes it to the type of sec. Consequently, 2010 is converted to true. In the expression fir + sec, fir is integral promoted to 1. Finally, the result is 2.
  • sumTem is not valid.

Thanks to the Compiler Explorer and GCC, here is the output of the program.


There is an interesting difference between sumInt and sumGen. The integral promotion of the true value happens in case of sumInt on the caller-side, but the integral promotion of the true value happens in case of sumGen in the arithmetic expression fir + sec. Here is the essential part of the program once more


auto sumInt = [](int fir, int sec) { return fir + sec; };            
auto sumGen = [](auto fir, auto sec) { return fir + sec; };         

int main() {
  sumInt(true, 2010);
  sumGen(true, 2010);


When I use the code snippet in C++ Insights (link to the program) it shows the difference. I only show the crucial part of the compiler-generated code.


class __lambda_1_15
  inline /*constexpr */ int operator()(int fir, int sec) const
    return fir + sec;

__lambda_1_15 sumInt = __lambda_1_15{};

class __lambda_2_15
  template<class type_parameter_0_0, class type_parameter_0_1>
  inline /*constexpr */ auto operator()(type_parameter_0_0 fir, type_parameter_0_1 sec) const
    return fir + sec;
  inline /*constexpr */ int operator()(bool fir, int sec) const
    return static_cast<int>(fir) + sec;                  // (2)

__lambda_2_15 sumGen = __lambda_2_15{};

int main()
  sumInt.operator()(static_cast<int>(true), 2010);       // (1)
  sumGen.operator()(true, 2010);


I assume you know that the compiler generates a function object out of a lambda. If you don't know it, Andreas Fertig wrote a few posts about his tool C++ Insights on my blog. One post is about lambdas: C++ Insights posts.

When you carefully study the code-snippet, you see the difference. sumInt performs the integral promotion on the call-side (1) but sumGen does it in the arithmetic expressions (2).

Honestly, this example was very enlightening for me and hopefully, also for you. A more typical use-case for template lambdas is the usage of containers in lambdas.

Template Parameter for Containers

The following program presents for lambdas accepting a container. Each lambda returns the size of the container.

// templateLambdaVector.cpp

#include <concepts>
#include <deque>
#include <iostream>
#include <string>
#include <vector>

auto lambdaGeneric = [](const auto& container) { return container.size(); };  
auto lambdaVector = []<typename T>(const std::vector<T>& vec) { return vec.size(); };
auto lambdaVectorIntegral = []<std::integral T>(const std::vector<T>& vec) { return vec.size(); };

int main() {

    std::cout << std::endl;
    std::deque deq{1, 2, 3};                     // (1)                 
    std::vector vecDouble{1.1, 2.2, 3.3, 4.4};   // (1)
    std::vector vecInt{1, 2, 3, 4, 5};           // (1)
    std::cout << "lambdaGeneric(deq): " << lambdaGeneric(deq) << std::endl;
    // std::cout << "lambdaVector(deq): " << lambdaVector(deq) << std::endl;                  ERROR
    // std::cout << "lambdaVectorIntegral(deq): " << lambdaVectorIntegral(deq) << std::endl;  ERROR

    std::cout << std::endl;
    std::cout << "lambdaGeneric(vecDouble): " << lambdaGeneric(vecDouble) << std::endl;
    std::cout << "lambdaVector(vecDouble): " << lambdaVector(vecDouble) << std::endl;
    // std::cout << "lambdaVectorIntegral(vecDouble): " << lambdaVectorIntegral(vecDouble) << std::endl;
    std::cout << std::endl;
    std::cout << "lambdaGeneric(vecInt): " << lambdaGeneric(vecInt) << std::endl;
    std::cout << "lambdaVector(vecInt): " << lambdaVector(vecInt) << std::endl;
    std::cout << "lambdaVectorIntegral(vecInt): " << lambdaVectorIntegral(vecInt) << std::endl;
    std::cout << std::endl;


lambdaGeneric can be invoked with any data type that has a member function size(). lambdaVector is more specific: it only accepts a std::vector. lambdaVectorIntegral uses C++20 concept std::integral. Consequently, it accepts only a std::vector using integral types such as int. To use it, I have to include the header <concepts>. I assume the small program is self-explanatory.


There is one feature in the program templateLambdaVector.cpp, that you have probably missed. Since C++17, the compiler can deduce the type of a class template from its arguments (1). Consequently, instead of the verbose std::vector<int> myVec{1, 2, 3} you can simply write std::vector myVec{1, 2, 3}.

What's next?

My next post will be about the remaining lambda improvements in C++20.


Thanks a lot to my Patreon Supporters: Meeting C++, Matt Braun, Roman Postanciuc, Venkata Ramesh Gudpati, Tobias Zindl, Marko, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Darshan Mody, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, espkk, Wolfgang Gärtner, Jon Hess, Christian Wittenhorst, Louis St-Amour, Stephan Roslen, Venkat Nandam, Jose Francisco, Douglas Tinkham, Lakshman, Kuchlong Kuchlong, Avi Kohn, Serhy Pyton, Robert Blanch, Kuma [], Truels Wissneth, Kris Kafka, Mario Luoni, and Neil Wang.




I'm happy to give online-seminars or face-to-face seminars world-wide. Please call me if you have any questions.

Standard Seminars 

Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.

Contact Me

Modernes C++,




My Newest E-Books

Course: Modern C++ Concurrency in Practice

Course: C++ Standard Library including C++14 & C++17

Course: Embedded Programming with Modern C++

Course: Generic Programming (Templates)

Course: C++ Fundamentals for Professionals

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code


Today 7046

Yesterday 10139

Week 38087

Month 7046

All 4627940

Currently are 194 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments