【问题标题】:Why lambda removes cv and ref?为什么 lambda 会删除 cv 和 ref?
【发布时间】:2017-03-03 12:20:07
【问题描述】:

给定一个 lambda:

auto f = [](const T& var){ return var; };

为什么f 的返回类型是T(不是const T&)?这在标准中的什么位置?

【问题讨论】:

  • 请注意,您可以使用[](const T& var) -> const auto& 获得const& 返回类型。

标签: c++ c++11 lambda language-lawyer return-type-deduction


【解决方案1】:

重点是:

  1. 使用auto进行返回类型推导采用模板类型推导规则。
  2. 返回类型被定义为按值传递;这意味着用于推导的表达式的引用性和顶级 cv 限定符(即var)将被忽略。

引用标准:

关于auto

如果占位符是自动类型说明符,则使用模板参数推导规则确定推导的类型 T' 替换 T。通过将出现的 auto 替换为新发明的类型模板参数 U 或如果初始化是复制列表初始化,则使用 std::initializer_list 从 T 中获取 P。使用从函数调用 ([temp.deduct.call]) 中的模板实参推导规则推导出 U 的值,其中 P 是函数模板形参类型,对应的实参是 e。如果扣除失败,则声明格式错误。否则,将推导出的U代入P得到T'。

关于template argument deduction from a function call的规则:

如果 P 不是引用类型:

  • 如果 A 是 cv 限定类型,则类型推导时忽略 A 类型的顶级 cv 限定符。

关于reference

如果表达式最初的类型为“对 T 的引用”([dcl.ref],[dcl.init.ref]),则在进行任何进一步分析之前将类型调整为 T。

所以对于var(即A)是const T&,推导出的返回类型将是T

【讨论】:

    【解决方案2】:

    这是关于 C++ 的一个更基本的问题。与 lambda 或 auto 无关。

    在 C++ 中,几乎在所有情况下,引用的行为都与非引用相同。这是经过深思熟虑的,并且基于对对象的引用确实应该等同于对象本身的想法。在下面的代码中,xy 之间没有真正的区别:

    int x  = 3;
    int &y = x;
    

    事实上,无法区分它们(通过decltype 除外)。它们都是int 类型的左值。如果调用foo(x)foo(y),编译器会将它们视为具有相同的类型和值类别,因此会选择相同的重载。

    我会将xy 解释为它们都是对同一个对象的引用。它们是同一个对象的两个不同“名称”。

    因此return xreturn y 是等价的,因此lambda 在推断其返回类型时不会关心&

    这解释了为什么 & 会被忽略。 C++ 尝试尽可能“忽略”&,正是为了将引用视为与原始对象完全等效。

    既然确定了我们是按值返回,而不是按引用返回,那么我们就可以理解为什么const 也被忽略了。对象的副本不需要是const。反过来考虑一下:我们可以将const int 参数作为函数的int 参数传递。

    【讨论】:

    • 小心...没有“参考对象”之类的东西;引用不是对象。此外,在您的示例中,对 foo 的两次调用都将是模棱两可的。而且,虽然答案可以被视为删除& 的非语言律师理由,但它并不能解释为什么删除const
    • 谢谢@bogdan,我应该测试我的重载集。我做了一些调整。我想这仍然不是一个理想的答案,但我确实认为有必要强调这种设计不仅仅是在 2011 年“凭空”写入 C++11 的东西,而是代表一种一致的设计在整个 C++ 中,引用通常应该与它们所引用的对象具有相同的行为(包括具有相同的类型和值类别)。
    【解决方案3】:

    在 C++11 或 14 中没有尾随返回类型的 auto 返回函数的返回类型推导就像你做的那样完成

    auto retval = /* return expression */
    

    auto总是推导出一个值类型。

    Lambda 是 C++11 中唯一的 auto 返回函数。在 C++14 中,它被扩展到其他函数(遵循类似的规则)。

    在 C++11 中,您可以使用 lambda 上的 ->decltype(var)->int const& 尾随返回类型来解决此问题。

    在 C++14 中你可以简单地->decltype(auto),它改变了返回类型推导规则

    auto retval = /* return expression */
    

    更像

    decltype(/* return expression */) retval = /* return expression */
    

    在您的情况下,这意味着不要删除 &const

    请注意,返回const& 参数是非常危险的,因为右值可以隐式转换为const&,并且引用生命周期扩展不会通过函数调用。所以:

    auto&& x = some_function();
    // or
    int const& x = some_function();
    

    即使some_function() 返回一个临时值也是安全的,同时假设f 的行为方式与您希望f 的行为方式相同:

    auto&& x = f( some_function() );
    // or
    int const& x = f( some_function() );
    

    生成xsome_function() 返回的临时值的悬空引用。

    这可能会出人意料地破坏事情,例如 for(:) 循环在后台隐式使用 auto&& 参数,并清理范围表达式初始化和迭代执行之间的临时性。

    这个答案还不完整,因为我没有在标准中添加参考。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-07
      • 2020-02-25
      • 1970-01-01
      相关资源
      最近更新 更多