使用棘手的lambda表达式的奇怪的未定义行为

时间:2013-08-16 15:01:40

标签: c++ c++11 lambda functional-programming

我一直在努力解决lambda表达式的一个问题,这个问题正在危害我的一个项目。我找到了一个解决方案,但我想确切地了解它的工作原理和原因,以及它是否可靠。

#include <iostream>
#include <functional>
#include <unordered_map>

typedef std::function<const int&(const int&)> Callback;

int f(int i, Callback callback) {
    if (i <= 2) return 1;
    return callback(i-1) + callback(i-2);
}

int main(void) {

    std::unordered_map<int, int> values;

    Callback callback = [&](const int& i) {
        if (values.find(i) == values.end()) {
            int v = f(i, callback);
            values.emplace(i, v);
        }
        return values.at(i);
    };

    std::cout << f(20, callback) << std::endl;

    return 0;

}

我知道这是计算第20个斐波纳契数的一种疯狂方法,但它是我能够详细说明的最紧凑的SSCCE。

如果我用g++ -O0编译上面的代码并执行程序,我得到6765,这实际上是第20个Fibonacci数。如果我使用-O1-O2-O3进行汇编,我会收到262144,这是垃圾。

如果我使用Valgrind(使用-O0 -g进行编译)对程序进行概要分析,我会在行Conditional jump or move depends on uninitialised value(s)上获得std::cout << f(20, callback) << std::endl;,但堆栈跟踪没有说明任何有用的内容。

我不知道为什么我最终得到了这个:

Callback callback = [&](const int& i) -> const int& {

通过这个小小的修改,一切都按预期编译,任何优化级别,Valgrind报告没有问题。

你能帮我理解发生了什么吗?

2 个答案:

答案 0 :(得分:12)

如果没有-> const int&,则lambda的返回类型为int。由于Callback的返回类型为const int&callback最终会返回对用于保存lambda表达式返回值的临时值的引用。未定义的行为。

对于这个简单的例子,简单的解决方法是根本不使用引用(Example at Coliru),但我认为你的真实代码处理的重量级对象比int更重要。遗憾的是,当您指定一个返回对其返回类型 引用的std::function的非引用的函数时,未指定std::function进行警告。

答案 1 :(得分:3)

Caseyanswer即将开启。我想补充一些东西。 (很明显,在凯西的好答案之上添加正在成为我的常见行为;-)。)实际上,我的评论是gd1Casey post的评论。

我猜OP正在使用g++ -std=c++1y,因为在C ++ 11中,返回的lambda类型设置为void。实际上,只有当lambda的主体包含单个return语句时,编译器才会推断出返回的类型。否则,编译器假定此类型为void。在C ++ 14中,返回类型的自动演绎(不仅对于lambda而且对函数而言)更强大。

正如人们可以看到n3582 - 那些被批准用于C ++ 14并且gcc的实现是参考的论文 - 陈述

  
    

普通汽车永远不会推断出参考,[...]

  

在OP的示例中,返回的类型为int(不是const int&)。我相信可以使用-> decltype(auto)让自动扣除产生参考。