2

How can I explicitly specify the type of a captured variable for a lambda in C++?

For example, assume I have a function that takes a universal reference and I want to perfect-forward it into a lambda.

I found out, that I can use a std::tuple for this as shown below, but I wonder if there is a more succinct way.

template<typename T>
auto returns_functor (T&& value)
{
  return [value = std::tuple<T> (std::forward<T> (value))] ()
  {
    /* use std::get<0> (value) */
  };
}

Related: Capturing perfectly-forwarded variable in lambda (the accepted answer there suggests this is a different question, but further answers give essentially the above solution).

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
levzettelin
  • 2,600
  • 19
  • 32
  • What about calling the lambda with the value instead of capturing it? – Aykhan Hagverdili May 26 '19 at 15:44
  • 1
    If you keep it a `T` what virtue is there to specify it? – StoryTeller - Unslander Monica May 26 '19 at 16:09
  • 1
    What's wrong with `value = std::forward(value)`? – HolyBlackCat May 26 '19 at 17:10
  • 2
    @HolyBlackCat Wouldn't that always capture by copy? – interjay May 26 '19 at 17:22
  • That doesn't look safe because the rvalue reference will probably be invalid when your function returns, yet you are capturing it into a lambda and returning the lambda. Invoking the lambda will try to use an invalid reference. e.g. `lambda = returns_functor(std::string());` When `returns_functor` returns, the string is destructed, and calling the lambda will use a destroyed object. – Raymond Chen May 26 '19 at 17:36
  • 1
    @RaymondChen In that example you pass in an rvalue reference, so it will be moved into a `tuple` and there are no lifetime issues. The possible issue is if you pass in an lvalue reference and then let the lambda outlive it. – interjay May 26 '19 at 17:39
  • @interjay Duh. Thanks for fixing. The issue still stands with the lvalue reference, thought that's a bit more manageable because the caller has a name for the thing that must remain alive. – Raymond Chen May 26 '19 at 18:07
  • @interjay Why would it? It should copy or move depending on the value category of the argument I think? – HolyBlackCat May 26 '19 at 18:13
  • 1
    @HolyBlackCat It would copy or move, but it would never hold a reference. So it can't be considered perfect forwarding. Besides, if all you needed was to copy or move, then all you'd need to specify was `[value]`. – interjay May 26 '19 at 18:22
  • By default, they're equivalent to using `auto` type deduction. You can add `&` (and maybe `&&` - not certain) in front of the capture variable name to make it capture by reference instead of by value. I believe you can add `const` as well (but I'm not certain). – Cruz Jean May 26 '19 at 20:54
  • @CruzJean: You cannot use `&&` or `const` there. – Davis Herring May 27 '19 at 03:05

1 Answers1

0

Another way of capturing rvalue ( universal reference) is as follows,

template< typename T>
T getRvalue( T&& temp)
{
  auto lambda = [ rTemp = std::move( temp)]() mutable
  {
     return T( std::forward< T>( rTemp));
  };

  return lambda();
}

std::move() allows to capture objects of movable type those can't be captured by copy. mutable is very important here in lambda, until mutable is not used, objects captured by lambda can't be modified and compile will generate following error,

cannot conver 'rTemp (type 'const T') to type 'std::remove_reference<T>::type&' {aka 'T&'}
return T( std::forward< T>( rTemp));

Now about first question,
"How can I explicitly specify the type of a captured variable for a lambda in C++?"

So It is not required to specify the type, compile will do it, see following link,

https://en.cppreference.com/w/cpp/language/lambda

It states A capture with an initializer acts as if it declares and explicitly captures a variable declared with type auto, whose declarative region is the body of the lambda expression. In simple words, it means auto variable get deduced according to initializer and doesn't requires to specify it's type explicitly, for example

int x = 1;
auto lambda = [ y = x + 2](){ std::cout<< "y = "<< y<< std::endl;};

Here y would be deduced as int by initializer x + 2.

Vikas Awadhiya
  • 290
  • 1
  • 8