使用从lambda创建的std :: function的奇怪返回行为(C ++) [英] Odd return behavior with std::function created from lambda (C++)

查看:223
本文介绍了使用从lambda创建的std :: function的奇怪返回行为(C ++)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如果函数返回一个引用,但是返回类型没有被明确地调用作为一个lambda,我遇到了从lambdas创建std ::函数的麻烦。看起来,std ::函数是精确的,没有警告,但调用它时,返回一个值,当一个引用,导致东西爆炸。这是一个非常有用的例子:

  #include< iostream> 
#include< vector>
#include< functional>

int main(){
std :: vector< int> v;
v.push_back(123);
std :: function< const std :: vector< int>&(const std :: vector< int>& callback =
[](const std :: vector< int>& in){return in;};
std :: cout<<回调(v).at(0)< std :: endl;
return 0;
}

这会打印垃圾,但是如果修改lambda以显式返回一个const参考它工作正常。我可以理解编译器认为lambda是返回值没有hint(当我最初遇到这个问题,lambda是直接返回从一个返回一个const引用的函数的结果,在这种情况下,我会认为const引用返回的lambda将被推导,但显然不是)。我惊讶的是,编译器让std ::函数从不匹配的返回类型的lambda构造。这是预期的行为吗?我缺少标准中的一些东西,允许这种不匹配发生?我看到这个与g ++(GCC)4.8.2,没有尝试过任何其他。



谢谢!



当lambda的返回类型为推断,参考和cv资格。所以返回类型

  [](const std :: vector< int>& in){return in;}; 

只是 std :: vector< int> std :: vector< int> const& 。因此,如果我们删除lambda和 std :: function 部分代码,我们有效地具有:

  std :: vector< int> lambda(std :: vector< int> const& in)
{
return in;
}

std :: vector< int>常数& callback(std :: vector< int> const& in)
{
return lambda(in);
}

lambda 临时。它有效地只是复制其输入。这个临时是 callback 中的引用返回。但是在 return 语句中绑定到引用的临时值不具有它们的生存期延长,因此在return语句的末尾销毁临时。因此,此时:

 回调(v).at(0)
------- ---- ^

我们对被破坏的 v



解决方案是明确指定作为参考的lambda的返回类型:

  [](const std :: vector< int>& in) - > const std :: vector< int>& {return in;} 
[](const std :: vector< int>& in) - > decltype(auto){return in;} // C ++ 14



现在没有副本,没有临时,没有悬挂引用,没有未定义的行为。



谁有过错?



至于这是否是预期的行为,答案其实是有。 std :: function 的可构造性条件是[func.wrap.func.con]:

$对于参数类型 ArgTypes ... 和返回类型,b $ b

f 是Callable(20.9.12.2) R


其中,[func.wrap.func] p>


F 的可调用对象 f $ c>是可调用的参数类型 ArgTypes 并返回类型 R b $ b INVOKE(f,declat< ArgTypes>()...,R),被认为是未评估的操作数(20.9.2)。


其中,[func.require],强调我的:


定义 INVOKE(f,t1,t2,...,tN,R) static_cast< ; void>(INVOKE(f,t1,t2,...,tN)) if R cv void ,否则 INVOKE(f,t1,t2,...,tN) code> R


>

  T func(); 
std :: function< T const&()>包装(func);

这实际上符合所有标准的要求: INVOKE(func)格式正确,并且返回 T T 可转换为 T const& 。所以这不是一个gcc或者clang的bug。这可能是一个标准缺陷,因为我不明白为什么你会想要允许这样的建设。这将永远不会有效,因此,措辞应该可能要求如果 R 是引用类型,则 F 也必须返回引用类型。


I'm having trouble with std::functions created from lambdas if the function returns a reference but the return type isn't explicitly called out as a lambda. It seems that the std::function is created fine with no warnings, but upon calling it, a value is returned when a reference is expected, causing things to blow up. Here's a very contrived example:

#include <iostream>
#include <vector>
#include <functional>

int main(){
   std::vector<int> v;
   v.push_back(123);
   std::function<const std::vector<int>&(const std::vector<int>&)> callback =
      [](const std::vector<int> &in){return in;};
   std::cout << callback(v).at(0) << std::endl;
   return 0;
}

This prints out garbage, however if the lambda is modified to explicitly return a const reference it works fine. I can understand the compiler thinking the lambda is return-by-value without the hint (when I originally ran into this problem, the lambda was directly returning the result from a function that returned a const reference, in which case I would think that the const reference return of the lambda would be deducible, but apparently not.) What I am surprised by is that the compiler lets the std::function be constructed from the lambda with mismatched return types. Is this behavior expected? Am I missing something in the standard that allows this mismatch to occur? I'm seeing this with g++ (GCC) 4.8.2, haven't tried it with anything else.

Thanks!

解决方案

Why is it broken?

When the return type of a lambda is deduced, reference and cv-qualifications are dropped. So the return type of

[](const std::vector<int> &in){return in;};

is just std::vector<int>, not std::vector<int> const&. As a result, if we strip out the lambda and std::function part of your code, we effectively have:

std::vector<int> lambda(std::vector<int> const& in)
{
    return in;
}

std::vector<int> const& callback(std::vector<int> const& in)
{
    return lambda(in);
}

lambda returns a temporary. It effectively is just copied its input. This temporary is bound the reference return in callback. But temporaries bound to a reference in a return statement do not have their lifetime extended, so the temporary is destroyed at the end of the return statement. Thus, at this point:

callback(v).at(0)
-----------^

we have a dangling reference to a destroyed copy of v.

The solution is to explicitly specify the return type of the lambda to be a reference:

 [](const std::vector<int> &in)-> const std::vector<int>& {return in;}
 [](const std::vector<int> &in)-> decltype(auto) {return in;} // C++14

Now there are no copies, no temporaries, no dangling references, and no undefined behavior.

Who's at fault?

As to whether this is expected behavior, the answer is actually yes. The conditions for constructibility of a std::function are [func.wrap.func.con]:

f is Callable (20.9.12.2) for argument types ArgTypes... and return type R.

where, [func.wrap.func]:

A callable object f of type F is Callable for argument types ArgTypes and return type R if the expression INVOKE (f, declval<ArgTypes>()..., R), considered as an unevaluated operand (Clause 5), is well formed (20.9.2).

where, [func.require], emphasis mine:

Define INVOKE(f, t1, t2, ..., tN, R) as static_cast<void>(INVOKE (f, t1, t2, ..., tN)) if R is cv void, otherwise INVOKE(f, t1, t2, ..., tN) implicitly converted to R.

So, if we had:

T func();
std::function<T const&()> wrapped(func);

That actually meets all the standard requirements: INVOKE(func) is well-formed and while it returns T, T is implicitly convertible to T const&. So this isn't a gcc or clang bug. This is likely a standard defect, as I don't see why you would ever want to allow such a construction. This will never be valid, so the wording should likely require that if R is a reference type then F must return a reference type as well.

这篇关于使用从lambda创建的std :: function的奇怪返回行为(C ++)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆