如果lambda表达式返回vector的一个元素,那么它的返回类型是什么?

29

考虑以下代码片段:

#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;
}

我本来期望得到同样的结果:在 f 中,v 是通过 const 引用传递的,所以 v[i] 应该有 const int& 类型。

然而,我得到了如下结果

 0 3

如果我不使用std :: function,一切都好:

#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;
}

输出:

3 3

因此我想知道:

  1. 在第二个片段中,lambda表达式f的返回类型是什么?fg是相同的吗?

  2. 在第一个片段中,构造std::function f时发生了什么,导致出现错误?


更具体地说,段错误发生在 f(3) 上,而不是 g(3) - huu
2
v 是通过 const 引用传递的,所以 v[i] 应该具有 const int& 类型。不,v 被引用捕获,因此 v[i] 是非 const 的 int&。我不确定在这种情况下是否应该有所区别。在 gcc 4.9.2 中似乎有预期的输出。 - eerorika
1
这对我来说看起来很有bug,但它也可能是lambda捕获引用变得悬空的后果。我得扫描标准。这就是为什么C++11很糟糕的原因。 - Lightness Races in Orbit
9
如果目标函数通过值返回T(你的第一个lambda),那么std::function<const T&(blah)>始终是未定义行为,因为它导致悬空引用。C++标准委员会正在考虑将其定义为非法。 - Jonathan Wakely
1
@LightningRacisinObrit,挂起的不是捕获引用,而是dangling指的是std::function<const int&(int)>::operator()返回值,因为它被绑定到了std::function<const int&(int)>::operator()(int)内部的一个局部临时变量上。 - Jonathan Wakely
显示剩余4条评论
3个回答

20
一个lambda表达式的返回类型使用auto自动推断规则,这会去掉引用。 (最初它使用了一组基于左值到右值转换(也删除了引用)的略微不同的规则,但这已由 DR 更改。)
因此,[&v](int i) { return v [i];};返回int。结果,在std :: function<const int&(int)> f = [&v](int i){return v [i];};中调用f()将返回一个悬空引用。将引用绑定到临时对象会延长临时对象的生存期,但在这种情况下,绑定发生在std :: function的内部机制深处,因此当f()返回时,临时对象已经消失。 g(3)是可以的,因为返回的const int &直接绑定到向量元素v [i],所以引用从未悬挂。

g(3) 返回 vector 内部存储中 int 的引用,对吗?那么理论上说,vector 是否可能重新分配内存并使该引用无效? - barney

13
如果 lambda 表达式返回一个 vector 的项,则返回类型是什么?这是错误的问题。您应该问的是,如果 lambda 表达式没有明确指定返回类型,那么返回类型是什么?答案在 C++11 5.1.2 [expr.prim.lambda] 段落 5 中给出,其中它说,如果 lambda 没有 return expr; 语句,则返回 void;否则返回表达式的类型,经过左值到右值转换(4.1)、数组到指针转换(4.2)和函数到指针转换(4.3)后的结果。左值到右值转换意味着,如果返回语句返回像 v[i] 这样的左值,它会衰减为 rvalue,即它只返回 int。所以你的代码问题在于lambda 返回一个临时值,但包装它的 std::function 绑定了对该临时值的引用。当你尝试打印出该值时,临时值已经消失了(它被绑定到一个不再存在的栈帧对象中),因此你有未定义的行为。解决方法是使用 std::function 或确保 lambda 返回一个有效的引用,而不是一个 rvalue。在 C++14 中,非 lambda 函数也可以使用返回类型推导。
auto f(std::vector<int>& v, int i) { return v[i]; }

这遵循与C++11关于lambda的规则类似(但不完全相同)的规则,因此返回类型是int。要返回引用,您需要使用:

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

@T.C.,是的,但这并不会改变结果,对吧?我有意引用了C++11 5.1.2,而不仅仅是5.1.2,因为问题标记为c++11。我知道DR被认为是针对C++11的修复,但由于它实际上并没有改变这里的结果,所以我选择了C++11中发布的措辞。 - Jonathan Wakely
没关系,在这种情况下无所谓。 - T.C.
我还是加了一条注释,以澄清实际上编译器应该在 C++11 模式下遵循 C++14 规则。感谢你找到相关的 DR :) - Jonathan Wakely
你还应该修改最后的陈述,因为如果我理解正确,在DR之后,这些规则与“auto”使用的规则是相同的。 - KRyan
在C++14中,你也可以使用decltype(auto)作为lambda的返回类型,类似于常规函数的自动类型推断。例如[&v](int i) -> decltype(auto) {return v[i];} - vsoftco
@KRyan,不是的,DR1048后,无论是Lambda函数还是普通函数都不使用"C++11规则"。 Lambda函数和普通函数都使用[temp.deduct.call]中的规则,这些规则与C++11的[expr.prim.lambda]中的规则不同。���们是相似但不完全相同的。 - Jonathan Wakely

5
  1. 我认为第一个lambda函数可能会返回int或者int&¹。由于引用v并不是指向常量对象,所以不会是const int&(即使捕获本身不可变也无关紧要,因为这是引用本身)

  2. 当绑定到常量引用时,临时对象的生命周期将延长到封闭作用域的末尾


¹ 我稍后会深入研究。现在,按照通常的推导直觉来说,auto将推导出int,而auto&将推导出int&,因此我预计实际返回类型是int。如果有人比我先完成,那就更好了。我几个小时内没有时间。

请参见@milleniumbug提供的测试:Live On Coliru


谢谢,@milleniumbug。我能编辑它吗?(如果我没有回应,您可以将其编辑掉)。那就不能很好地解释编译器的行为了。_(唉,没时间专心)_ - sehe
v 的常量性并不是很重要,lambda 表达式的推导返回类型会衰减(就像 T.C. 的回答所说的 auto 返回一样),因此无论 v[i] 是否为 const,其类型都是 int - Jonathan Wakely

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接