Lambda in C++ 17 has new changes in its feature list. They are now better and more suited to fit in more places, like, in constant expressions. The changes are explained below.
By default, Lambda is constexpr
This means any compile-time constant expressions can now use lambda expressions. Such lambda shall be executed by compiler in compile time. However, it should be noted that for C++17 compiler to execute a lambda, firstly, all inputs should be available and secondly, there should be no code which have run-time dependencies. For instance, there should be no virtual functions, no static variables, no try-catch, no new-delete and anything which a compiler can not run.
The following program demonstrates how a lambda is called to assign value to constexpr integer “a”.
#include <iostream> //main header
using namespace std; //namespace
int main()
{
auto sqr = [](auto i) { return i*i;};
constexpr int avar = sqr(3);
cout << avar << endl;
return 0;
}
Output
9
The above code shall not compile in C++11 or C++14 and will result in following error.
Lambda explicitly declared with constexpr keyword
Although lambda by default is constexpr in C++17, however, a program can use them for run-time expressions also. In case, these lambdas are valid for compile-time context, then it is a good practice to use constexpr keyword in the declaration. However, the program can still use them for run-time contexts.
The following example demonstrates how constexpr lamda is evaluating value at run-time.
#include <iostream> //main header
using namespace std;//namespace
int main()
{
auto sqr = [](int ivar) constexpr
{
return ivar*ivar;
};
int ivar;
cin >> ivar;
int avar = sqr(ivar);
//Taking non-constant input
cout << avar << endl;
return 0;
}
Output
Advantage of constexpr with lambda declaration
In case, a lambda declaration is having constexpr keyword then it should not use any statement which compiler cannot execute. The compiler cannot accept any statement inside such lambda. The following program uses a “cout” statement inside an explicitly constexpr lambda. The result is compilation error.
#include <iostream> //main header
using namespace std; //namespace
int main()
{
auto sqr = [](int i) constexpr
{
cout << "ABC\n";
return i*i;
};
constexpr int avar = sqr(3);
cout << avar << endl;
return 0;
}
Error message is as following.
Lambda inside member functions
When class member functions contains lambda then, by default, they do not have access to “this” pointer. There is always a possibility that a lambda may outlive “this” pointer. Therefore, to use “this” pointer, lambda has to be explicitly capture it.
The following program tries to directly use a this pointer in lambda and therefore fails at compilation.
#include <iostream> //main header
using namespace std; //namespace
class MainFunda
{
public:
void funda()
{
auto l0 = []()
{
cout << this << endl;
};
l0();
}
};
int main()
{
MainFunda a1;
cout << &a1 << endl;
a1.funda();
return 0;
}
The compiler generates following error message.
C++11 way of passing this pointer to lambda
In C++11, the program has to pass “this” pointer either by value or by reference. The method is just like a workaround because, in all cases, this will pass the underlying object as reference. And this reference may become invalid when underlying object destroys, however, it is possible that lambda may outlive the object.
The following program show 3 ways of passing this pointer for capture. From the output, it is clear that ultimately same object is passed and all 3 cases.
#include <iostream> //main header
using namespace std; //namespace
class MainFunda
{
public:
MainFunda()
{
cout << "Constructor\n" ;
}
MainFunda(const MainFunda&&)
{
cout << "Copy Constructor\n" ;
}
~MainFunda()
{
cout << "Destructor\n" ;
}
void funda()
{
auto l1 = [this](){cout << this << endl;};
auto l2 = [=] (){cout << this << endl;};
auto l3 = [&] (){cout << this << endl;};
l1();
l2();
l3();
}
};
int main()
{
MainFunda a1;
cout << &a1 << endl;
a1.funda();
return 0;
}
The output prints exactly same “this” pointer address in all cases.
C++14 way of passing this pointer to lambda
C++14 way of passing “this” pointer to lambda is slightly better as it uses generic capture mode. Therefore, ultimately a copy of object goes inside the lambda as capture. However, this method looks very ugly.
The following program shows that on passing “this” pointer, firstly, the corresponding copy-constructor is called. Finally, a new object goes inside lambda capture.
#include <iostream> //main header
using namespace std; //namespace
class MainFunda
{
public:
MainFunda()
{
cout << "Constructor\n" ;
}
MainFunda(const MainFunda& rhs)
{
cout << "Copy Constructor\n" ;
}
void funda()
{
auto l0 = [thiscopy=*this]()
{
cout << &thiscopy << endl;
};
l0();
}
~MainFunda()
{
cout << "Destructor\n" ;
}
};
int main()
{
MainFunda a1;
cout << &a1 << endl;
a1.funda();
return 0;
}
The output shows that address of original object is different than address shown by “this” pointer. Clearly, copy constructor call is also happening during the process of capture.
C++17 way of passing this pointer to lambda
C++17 has the cleanest approach. It passes “[*this]” in capture. This syntax causes compiler to first call copy constructor for creating a new object. And then finally, the new object goes inside lambda. The following program demonstrates this new syntax.
#include <iostream> //main header
using namespace std; //namespace
class MainFunda
{
public:
MainFunda()
{
cout << "Constructor\n" ;
}
MainFunda(const MainFunda& rhs)
{
cout << "Copy Constructor\n" ;
}
void funda()
{
auto l0 = [*this]()
{
cout << this << endl;
};
l0();
}
~MainFunda()
{
cout << "Destructor\n" ;
}
};
int main()
{
MainFunda a1;
cout << &a1 << endl;
a1.funda();
return 0;
}
The output shows that just like, the case of C++14, a new object goes inside lambda. Hence, the address of “this” pointer inside lambda is different than address of original object.