Изменен тип вычета лямбда N3638 .и теперь тип возврата lambda
использует правила удержания типа возврата auto
, которые лишают референтности.Следовательно, [&]() { return str;};
возвращает string
.В результате в void f(std::function<const std::string&()> fn)
вызов fn()
возвращает висячую ссылку.Привязка ссылки к временному объекту продлевает срок службы временного объекта, но в этом случае привязка произошла глубоко внутри механизма std::function
, поэтому к моменту возврата f()
временное пространство уже исчезло.
Правило лямбда-вывода
В типах возврата auto и lambda используются несколько разные правила для определения типа результата из выражения.auto использует правила из 17.9.2.1 [temp.deduct.call], который явно отбрасывает cv-квалификацию верхнего уровня во всех случаях, тогда как лямбда-тип возврата основан на преобразовании lvalue-to-rvalue, которое отбрасывает 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 предназначен только для предоставления константного типа, если вы явно запрашиваете его, а лямбда-тип возвращаемого значения обычно должен быть типом выражения.
Даниэль Крюглер:
Еще одно несоответствие: с autoиспользование braced-init-list
может вывести специализацию std::initializer_list;
. Было бы полезно, если бы это можно было сделать для лямбда-типа возврата.
Дополнительные примечания, февраль 2014 года:
EWG отметила, что g ++ и clang по-разному относятся к этому примеру, и передала его обратно CWG для разрешения.
Давайте посмотрим, что выводится в вашем коде :
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&
, вы можете проверить типы возврата ваших лямбд с помощью следующего кода:
//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;
}