使用lambda(C ++)创建的std :: function的奇数返回行为

时间:2015-09-30 16:48:52

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

如果函数返回引用但是返回类型没有明确地作为引用调用,那么我在使用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看到了这个,没有尝试过其他任何东西。

谢谢!

3 个答案:

答案 0 :(得分:13)

为什么会损坏?

当推导出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> const& callback(std::vector<int> const& in)
{
    return lambda(in);
}

lambda返回一个临时的。它实际上只是复制了它的输入。此临时值绑定了callback中的引用返回。但是绑定到return语句中的引用的临时值没有延长它们的生命周期,因此临时值在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...可调用(20.9.12.2)并返回类型R

其中,[func.wrap.func]:

  

f类型的可调用对象F对于参数类型ArgTypes Callable ,如果表达式则返回类型R   INVOKE (f, declval<ArgTypes>()..., R),被视为未评估的操作数(第5条),很好   形成(20.9.2)。

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

  

如果INVOKE(f, t1, t2, ..., tN, R) cv static_cast<void>(INVOKE (f, t1, t2, ..., tN)),则R定义为void,否则INVOKE(f, t1, t2, ..., tN) 会隐式转换为{{ 1}}

所以,如果我们有:

R

实际上符合所有标准要求:T func(); std::function<T const&()> wrapped(func); 格式正确,当它返回INVOKE(func)时,T 可隐式转换为{{1} }。所以这不是一个gcc或clang bug。这可能是一个标准缺陷,因为我不明白为什么你会想要允许这样的结构。这将永远无效,因此措辞可能要求如果T是引用类型,则T const&也必须返回引用类型。

答案 1 :(得分:2)

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

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

这里没有问题,因为C ++的规则延长了临时的生命周期。 但是,以下内容具有未定义的行为:

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

这是因为终身延期不适用于此处。临时文件在std::function operator()内创建,并在比较之前销毁。

因此,std::function的构造函数可能不应单独依赖Callable,而是强制执行额外的限制,即将rvalue隐式转换为const左值以便绑定到引用是被禁止的。或者,可以将Callable更改为永不允许此转换,但代价是不允许某些安全使用(如果仅因为有效期延长)。

为了使事情变得更复杂,只要您不访问悬空参考的目标,上述示例中的fWrapped()就可以完全安全地通话。

答案 2 :(得分:0)

如果您使用:

'[value="Accept"]'

在你的lambda中它会起作用。

这会使你的lambda的返回类型为return std::ref(in); ,可以隐式转换为std::reference_wrapper<std::vector<int>>