未指定Lambda函数的返回类型时出现段错误

时间:2019-06-18 16:04:40

标签: c++ c++11 lambda

我有以下示例代码:

#include <functional>
#include <iostream>
#include <string>

void f(std::function<const std::string&()> fn) {
  std::cout << "in f" << std::endl;
  std::cout << "str: " << fn() << std::endl;
}

int main() {
  std::string str = "a";

  auto fn1 = [&]() { return str; };
  auto fn2 = [&]() { const std::string& str2 = str; return str2; };
  auto fn3 = [&]() -> const std::string& { return str; };

  std::cout << "in main" << std::endl;
  std::cout << "fn1: " << fn1() << std::endl;
  std::cout << "fn2: " << fn2() << std::endl;
  std::cout << "fn3: " << fn3() << std::endl;

  f(fn1);  // Segfaults
  f(fn2);  // Also segfaults
  f(fn3);  // Actually works

  return 0;
}

当我第一次写这篇文章时,我希望在fn1()内调用f()会正确返回对strmain的引用。假设str一直分配到f()返回之后,这对我来说似乎还不错。但是实际上发生的是尝试访问fn1()段错误中的f()的返回。

fn2()也会发生同样的事情,但是令人惊讶的是fn3()可以正常工作。

鉴于fn3()有效而fn1()无效,关于C ++如何推导lambda函数的返回值,我是否缺少某些东西?那将如何创建此段错误?

为记录起见,如果我运行此代码,则输出如下:

仅呼叫f(fn3)

in main
fn1: a
fn2: a
fn3: a
in f
str: a

仅呼叫f(fn2)

in main
fn1: a
fn2: a
fn3: a
in f
Segmentation fault (core dumped)

仅呼叫f(fn1)

in main
fn1: a
fn2: a
fn3: a
in f
Segmentation fault (core dumped)

2 个答案:

答案 0 :(得分:8)

没有尾随返回类型的lambda,如下所示:

[&](){return str;};

等效于:

[&]()->auto{return str;};

因此此lambda返回str的副本。

调用std::function对象将得到以下等效代码:

const string& std_function_call_operator(){
    // functor = [&]->auto{return str;};

    return functor();
    }

调用此函数时,将str复制到一个临时文件中,将引用绑定到该临时文件,然后销毁该临时文件。因此,您将获得著名的悬挂参考。这是一个非常经典的场景。

答案 1 :(得分:1)

lambda的返回类型推论已更改N3638。现在lambda的返回类型使用auto返回类型推导规则,该规则去除了参考性。 因此,[&]() { return str;};返回string。结果,在无效f(std::function<const std::string&()> fn)中调用fn()会返回一个悬空引用。将对引用的引用绑定到临时对象可以延长临时对象的生命周期,但是在这种情况下,绑定发生在std::function的机器内部,因此f()返回时,临时对象已经不存在了。

lambda扣除规则

  

auto和lambda返回类型使用略有不同的规则   根据表达式确定结果类型。自动使用中的规则   17.9.2.1 [temp.deduct.call],在所有情况下,lambda返回类型都是基于的,它明确地删除了所有顶级cv资格   在左值到右值转换时,仅会降低cv资格   对于非类类型。结果:

struct A { };

const A f();

auto a = f();               // decltype(a) is A
auto b = []{ return f(); }; // decltype(b()) is const A This seems like an unnecessary inconsistency.
     

约翰·斯派塞:

     

差异是故意的; auto仅用于提供const   键入是否明确要求,而lambda返回类型应   通常是表达式的类型。

     

丹尼尔·克鲁格勒:

     

另一个不一致的地方:使用自动功能时,braced-init-list的使用可以推断出   std::initializer_list;的专业化,如果   对于lambda返回类型也可以这样做。

     

附加说明,2014年2月:

     

EWG指出g ++和clang在处理此示例方面有所不同   并将其返回给CWG进行解决。

让我们看看您的code中得出的结论:

fn1: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >

fn2: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >

fn3: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&

您可以看到只有最后一个实际上是const&

您可以使用以下代码检查lambda的返回类型:

//https://stackoverflow.com/a/20170989/10933809
#include <functional>
#include <iostream>
#include <string>

void f(std::function<const std::string&()> fn) {
  std::cout << "in f" << std::endl;
  std::cout << "str: " << fn() << std::endl;
}
#include <type_traits>
#include <typeinfo>
#ifndef _MSC_VER
#   include <cxxabi.h>
#endif
#include <memory>
#include <string>
#include <cstdlib>

template <class T>
std::string
type_name()
{
    typedef typename std::remove_reference<T>::type TR;
    std::unique_ptr<char, void(*)(void*)> own
           (
#ifndef _MSC_VER
                abi::__cxa_demangle(typeid(TR).name(), nullptr,
                                           nullptr, nullptr),
#else
                nullptr,
#endif
                std::free
           );
    std::string r = own != nullptr ? own.get() : typeid(TR).name();
    if (std::is_const<TR>::value)
        r += " const";
    if (std::is_volatile<TR>::value)
        r += " volatile";
    if (std::is_lvalue_reference<T>::value)
        r += "&";
    else if (std::is_rvalue_reference<T>::value)
        r += "&&";
    return r;
}
int main() {
  std::string str = "a";

  auto fn1 = [&]() { return str; };
  auto fn2 = [&]() { const std::string& str2 = str; return str2; };
  auto fn3 = [&]() -> const std::string& { return str; };

  std::cout << "in main" << std::endl;
  std::cout << "fn1: " << fn1() << std::endl;
  std::cout << "fn2: " << fn2() << std::endl;
  std::cout << "fn3: " << fn3() << std::endl;
auto f1=fn1();
  std::cout << "fn1: " << type_name<decltype(fn1())>() << std::endl;
  std::cout << "fn2: " << type_name<decltype(fn2())>() << std::endl;
  std::cout << "fn3: " << type_name<decltype(fn3())>() << std::endl;

  f(fn1);  // Segfaults
  f(fn2);  // Also segfaults
  f(fn3);  // Actually works

  return 0;
}