C++ Lambda Code Generation with Init Captures in C++ 14
Case 1 [x](){}
: The generated constructor will accept its argument by possibly const
-qualified reference to avoid unnecessary copies:
__some_compiler_generated_name(const int& x) : x_{x}{}
Case 2 [x&](){}
: Your assumptions here are correct, x
is passed and stored by reference.
Case 3 [x = 33](){}
: Again correct, x
is initialized by value.
Case 4 [p = std::move(unique_ptr_var)]
: The constructor will look like this:
__some_compiler_generated_name(std::unique_ptr<SomeType>&& x) :
x_{std::move(x)}{}
so yes, the unique_ptr_var
is "moved into" the closure. See also Scott Meyer's Item 32 in Effective Modern C++ ("Use init capture to move objects into closures").
There's less of a need to speculate, using cppinsights.io.
Case 1:
Code
#include <memory>
int main() {
int x = 33;
auto lambda = [x]() { std::cout << x << std::endl; };
}
Compiler generates
#include <iostream>
int main()
{
int x = 6;
class __lambda_5_16
{
int x;
public:
inline void operator()() const
{
std::cout.operator<<(x).operator<<(std::endl);
}
// inline /*constexpr */ __lambda_5_16(const __lambda_5_16 &) = default;
// inline /*constexpr */ __lambda_5_16(__lambda_5_16 &&) noexcept = default;
public: __lambda_5_16(int _x)
: x{_x}
{}
};
__lambda_5_16 lambda = __lambda_5_16(__lambda_5_16{x});
}
Case 2:
Code
#include <iostream>
#include <memory>
int main() {
int x = 33;
auto lambda = [&x]() { std::cout << x << std::endl; };
}
Compiler generates
#include <iostream>
int main()
{
int x = 6;
class __lambda_5_16
{
int & x;
public:
inline void operator()() const
{
std::cout.operator<<(x).operator<<(std::endl);
}
// inline /*constexpr */ __lambda_5_16(const __lambda_5_16 &) = default;
// inline /*constexpr */ __lambda_5_16(__lambda_5_16 &&) noexcept = default;
public: __lambda_5_16(int & _x)
: x{_x}
{}
};
__lambda_5_16 lambda = __lambda_5_16(__lambda_5_16{x});
}
Case 3:
Code
#include <iostream>
int main() {
auto lambda = [x = 33]() { std::cout << x << std::endl; };
}
Compiler generates
#include <iostream>
int main()
{
class __lambda_4_16
{
int x;
public:
inline void operator()() const
{
std::cout.operator<<(x).operator<<(std::endl);
}
// inline /*constexpr */ __lambda_4_16(const __lambda_4_16 &) = default;
// inline /*constexpr */ __lambda_4_16(__lambda_4_16 &&) noexcept = default;
public: __lambda_4_16(int _x)
: x{_x}
{}
};
__lambda_4_16 lambda = __lambda_4_16(__lambda_4_16{33});
}
Case 4 (unofficially):
Code
#include <iostream>
#include <memory>
int main() {
auto x = std::make_unique<int>(33);
auto lambda = [x = std::move(x)]() { std::cout << *x << std::endl; };
}
Compiler generates
// EDITED output to minimize horizontal scrolling
#include <iostream>
#include <memory>
int main()
{
std::unique_ptr<int, std::default_delete<int> > x =
std::unique_ptr<int, std::default_delete<int> >(std::make_unique<int>(33));
class __lambda_6_16
{
std::unique_ptr<int, std::default_delete<int> > x;
public:
inline void operator()() const
{
std::cout.operator<<(x.operator*()).operator<<(std::endl);
}
// inline __lambda_6_16(const __lambda_6_16 &) = delete;
// inline __lambda_6_16(__lambda_6_16 &&) noexcept = default;
public: __lambda_6_16(std::unique_ptr<int, std::default_delete<int> > _x)
: x{_x}
{}
};
__lambda_6_16 lambda = __lambda_6_16(__lambda_6_16{std::unique_ptr<int,
std::default_delete<int> >
(std::move(x))});
}
And I believe this last piece of code answers your question. A move occurs, but not [technically] in the constructor.
Captures themselves aren't const
, but you can see that the operator()
function is. Naturally, if you need to modify the captures, you mark the lambda as mutable
.