我有以下示例代码:
#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()
会正确返回对str
中main
的引用。假设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)
答案 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;
}