What is the return type of a lambda expression if

2019-01-18 18:08发布

Consider the following snippet:

#include <iostream>
#include <vector>
#include <functional>

int main() 
{
    std::vector<int>v = {0,1,2,3,4,5,6};
    std::function<const int&(int)> f = [&v](int i) { return v[i];}; 
    std::function<const int&(int)> g = [&v](int i) -> const int& { return v[i];};

    std::cout << f(3) << ' ' << g(3) << std::endl;
    return 0;
}

I was expecting the same result: in f, v is passed by const reference, so v[i] should have const int& type.

However, I get the result

 0 3

If I do not use std::function, everything is fine:

#include <iostream>
#include <vector>
#include <functional>

int main() 
{
    std::vector<int>v = {0,1,2,3,4,5,6};
    auto f = [&v](int i) { return v[i];}; 
    auto g = [&v](int i) -> const int& { return v[i];};

    std::cout << f(3) << ' ' << g(3) << std::endl;
    return 0;
}

output:

3 3

Thus I'm wondering:

  1. In the second snippet, what is the return type of the lambda expression f? Is f the same as g?

  2. In the first snippet, what happened when the std::function f was constructed, causing the error?

3条回答
等我变得足够好
2楼-- · 2019-01-18 18:18

What is the return type of a lambda expression if an item of a vector is returned?

That's the wrong question.

You should be asking what is the return type of a lambda expression if it is not specified explicitly?.

The answer is given in C++11 5.1.2 [expr.prim.lambda] paragraph 5, where it says if the lambda has no return expr; statement it returns void, otherwise:

the type of the returned expression after lvalue-to-rvalue conversion (4.1), array-to-pointer conversion (4.2), and function-to-pointer conversion (4.3);

(See T.C.'s comment below for DR 1048 which changed this rule slightly, and compilers actually implement the changed rule, but it doesn't matter in this case.)

lvalue-to-rvalue conversion means that if the return statement returns an lvalue like v[i] it decays to an rvalue, i.e. it returns just int by value.

So the problem with your code is that the lambda returns a temporary, but the std::function<const int&(int)> that wraps it binds a reference to that temporary. When you try to print out the value the temporary has aalready gone away (it was bound to an object a stack frame that no longer exists) so you have undefined behaviour.

The fix is to use std::function<int(int)> or ensure the lambda returns a valid reference, not an rvalue.

In C++14 non-lambdas can also use return type deduction, e.g.:

auto f(std::vector<int>& v, int i) { return v[i]; }

This follows similar (but not identical) rules to the C++11 rules for lambdas, so the return type is int. To return a reference you need to use:

decltype(auto) f(std::vector<int>& v, int i) { return v[i]; }
查看更多
乱世女痞
3楼-- · 2019-01-18 18:31
  1. I assume the first lambda might return int or int&¹. Not const int& as the reference v is not to a const object (it doesn't matter that the capture itself is not mutable, since that's the reference itself)

  2. Life times of temporaries get extended to the end of the enclosing scope when bound to a const-ref


¹ I'll try to dive into that later. For now, I'm going with regular deduction intuition and say that auto would decuce int, whereas auto& would deduce int&, so I expect that the actual return type is int. If anyone beats me to it, all the better. I won't have time for a few hours

See the test supplied by @milleniumbug: Live On Coliru

查看更多
做自己的国王
4楼-- · 2019-01-18 18:33

The return type of a lambda uses the auto return type deduction rules, which strips the referenceness. (Originally it used a slightly different set of rules based on lvalue-to-rvalue conversion (which also removed the reference), but that was changed by a DR.)

Hence, [&v](int i) { return v[i];}; returns int. As a result, in std::function<const int&(int)> f = [&v](int i) { return v[i];};, calling f() returns a dangling reference. Binding a reference to a temporary extends the lifetime of the temporary, but in this case the binding happened deep inside std::function's machinery, so by the time f() returns, the temporary is gone already.

g(3) is fine because the const int & returned is bound directly to the vector element v[i], so the reference is never dangling.

查看更多
登录 后发表回答