【问题标题】:Odd return behavior with std::function created from lambda (C++)从 lambda (C++) 创建的 std::function 的奇怪返回行为
【发布时间】:2015-12-28 13:33:18
【问题描述】:

如果函数返回引用但返回类型未显式调用为引用,则我在使用从 lambdas 创建的 std::functions 时遇到问题。似乎 std::function 被创建得很好,没有任何警告,但是在调用它时,会在需要引用时返回一个值,从而导致事情崩溃。这是一个非常人为的例子:

#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;
}

这会打印出垃圾,但是如果将 lambda 修改为显式返回 const 引用,它就可以正常工作。我可以理解编译器认为 lambda 是按值返回而没有提示(当我最初遇到这个问题时,lambda 直接从返回 const 引用的函数返回结果,在这种情况下,我会认为lambda 的 const 引用返回是可推导出的,但显然不是。)令我惊讶的是编译器允许 std::function 从具有不匹配返回类型的 lambda 构造。这种行为是预期的吗?我是否遗漏了标准中允许发生这种不匹配的内容?我在 g++ (GCC) 4.8.2 中看到了这个,还没有尝试过其他任何东西。

谢谢!

【问题讨论】:

  • @Nawaz 你为什么删除你的答案?
  • Clang++ 3.7.0 也会打印垃圾(g++ 导致我出现分段错误)。
  • @jhnnslschnr 感谢您的检查 - 不知道为什么发布的答案被删除(它似乎是正确的)但本质是返回值的 lambda 将愉快地绑定到带有引用返回的 std::function类型,最终结果是 lambda 返回一个副本,而函数返回对临时副本的引用。我想这与从具有引用返回类型的函数返回局部变量没有什么不同。

标签: c++ c++11 lambda g++ std-function


【解决方案1】:

为什么坏了?

当推导 lambda 的返回类型时,引用和 cv 限定被删除。所以

的返回类型
[](const std::vector<int> &in){return in;};

只是std::vector&lt;int&gt;,而不是std::vector&lt;int&gt; const&amp;。因此,如果我们去掉代码中的 lambda 和 std::function 部分,我们实际上有:

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 返回一个临时值。它实际上只是复制了它的输入。这个临时绑定在callback 中的引用返回。但是绑定到return 语句中的引用的临时对象没有延长其生命周期,因此临时对象在返回语句结束时被销毁。因此,此时:

callback(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]:

f 对于参数类型 ArgTypes... 和返回类型 R 是可调用的 (20.9.12.2)。

哪里,[func.wrap.func]:

类型为F 的可调用对象f 对于参数类型ArgTypes 和返回类型R可调用,如果表达式 INVOKE (f, declval&lt;ArgTypes&gt;()..., R),被认为是未计算的操作数(第 5 条),很好 形成(20.9.2)。

哪里,[func.require],强调我的:

如果Rvoid,则将INVOKE(f, t1, t2, ..., tN, R) 定义为static_cast&lt;void&gt;(INVOKE (f, t1, t2, ..., tN)),否则INVOKE(f, t1, t2, ..., tN) 隐式转换为R

所以,如果我们有:

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

这实际上满足了所有标准要求:INVOKE(func) 格式正确,虽然它返回 T,但 T 可隐式转换为 T const&amp;。所以这不是 gcc 或 clang 错误。这可能是一个标准缺陷,因为我不明白您为什么要允许这样的构造。这将永远有效,因此措辞可能要求如果R 是引用类型,那么F 也必须返回引用类型。

【讨论】:

  • 在 C++14 中它可以是-&gt; decltype(auto)
  • 类型推论没错,但你没有回答另一半问题。简化:为什么允许function&lt;const T &amp;()&gt; 绑定到按值返回T 的lambda?这将总是创建一个悬空引用。我想说这要么是库错误,要么是标准中的疏忽,因为这种情况可以在编译时使用 decltype 进行检查。
  • @ArneVogel 标准缺陷。 gcc/clang 完全符合允许该构造的要求。添加了措辞。
  • 我们只是直接用 std::function 解决了同样的问题,将一个返回值的函数放入一个返回 const-ref 的 std::function 中。 MSVC 2015 正确诊断它,MSVC 2013 只为函数指针提取它,而不是 std::bind 创建的对象,gcc 和 clang 都没有真正注意到这一点。这是实施质量,还是需要提出的标准缺陷?
【解决方案2】:

我对@9​​87654322@ 构造函数进行了一些自己的搜索。这部分似乎是对std::function 和标准Callable concept 交互的疏忽。 std::function&lt;R(Args...)&gt;::function&lt;F&gt;(F) 要求 FCallableR(Args...),这本身似乎是合理的。 Callable 对于R(Args...) 需要F 的返回类型(当给定类型为Args... 的参数可以隐式转换为R 时,这本身似乎也是合理的。现在当Rconst R_ &amp; 时,这将允许 R_const R_ &amp; 的隐式转换,因为允许 const 引用绑定到右值。这不一定不安全。例如,考虑一个函数 f(),它返回一个 int,但被认为是可调用的 @ 987654340@.

const int &result = f();
if ( f == 5 )
{
   // ...
}

这里没有问题,因为 C++ 有延长临时对象生命周期的规则。 然而,以下行为未定义:

std::function<const int &()> fWrapped{f};
if ( fWrapped() == 5 )
{
   // ...
}

这是因为生命周期延长不适用于此处。临时是在std::functionoperator() 内部创建的,并在比较之前被销毁。

因此,std::function 的构造函数可能不应该单独依赖Callable,而是强制执行附加限制,即禁止将右值隐式转换为const 左值以绑定到引用。或者,Callable 可以更改为不允许这种转换,代价是不允许某些安全使用(如果只是因为生命周期延长)。

为了让事情变得更复杂,上面示例中的fWrapped() 调用是完全安全的,只要您不访问悬空引用的目标。

【讨论】:

    【解决方案3】:

    如果你使用:

    return std::ref(in);
    

    在你的 lambda 中它会起作用。

    这将使您的 lambda 的返回类型为 std::reference_wrapper&lt;std::vector&lt;int&gt;&gt;,可隐式转换为 std::vector&lt;int&gt;&amp;

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-01-24
      • 1970-01-01
      • 1970-01-01
      • 2011-06-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多