完美转发返回值与auto&&

时间:2018-02-28 16:10:13

标签: c++ language-lawyer

C ++模板:完整指南(第2版)中考虑此引用:

protected $hidden = ['password', 'remember_token'];
     

请注意,使用decltype(auto) ret{std::invoke(std::forward<Callable>(op), std::forward<Args>(args)...)}; ... return ret; 声明ret不正确。作为一个   引用,auto&&延长返回值的生命周期,直到   范围的结尾但不超出auto&&语句的范围   函数的调用者

作者说return不适合完美转发返回值。但是,auto&&是否也形成对xvalue / lvalue的引用? IMO,decltype(auto)然后遭受同样的问题。那么,作者的观点是什么?

修改

上面的代码片段应该放在这个函数模板中。

decltype(auto)

4 个答案:

答案 0 :(得分:5)

这里有两个扣除。一个来自返回表达式,一个来自std::invoke表达式。因为decltype(auto) is deduced to be the declared type for unparenthesized id-expression,我们可以专注于std::invoke表达式的推论。

引自[dcl.type.auto.deduct] paragraph 5:

  

如果占位符是decltype(auto) 类型说明符,则T应仅为占位符。 T推导出的类型按照[dcl.type.simple]中的描述确定,好像edecltype的操作数。

引自[dcl.type.simple] paragraph 4

  

对于表达式edecltype(e)表示的类型定义如下:

     
      
  • 如果e是未命名的id-expression,命名结构化绑定([dcl.struct.bind]),decltype(e)是结构化绑定规范中给出的引用类型声明;

  •   
  • 否则,如果e是未加密码的 id-expression 或未加密码的类成员访问,decltype(e)是{{1}命名的实体的类型}}。如果没有这样的实体,或者e命名了一组重载函数,则该程序格式不正确;

  •   
  • 否则,如果e是xvalue,则edecltype(e)T&&的类型为T;

  •   
  • 否则,如果e是左值,则edecltype(e)T&的类型为T;

  •   
  • 否则,edecltype(e)的类型。

  •   

如果e是prvalue,则注意decltype(e)被推断为T而不是T&&。这与e不同。

因此,如果auto&&是prvalue,则std::invoke(std::forward<Callable>(op), std::forward<Args>(args)...)的返回类型不是引用,即按值返回,Callable被推断为相同类型而不是引用,它完美地转发了按值返回的语义。

答案 1 :(得分:4)

  

但是,decltype(auto)是否也形成对xvalue / lvalue的引用?

没有

部分decltype(auto)的神奇之处在于它知道ret是左值,so it will not form a reference

如果您编写了return (ret),它确实已经解析为引用类型,并且您将返回对局部变量的引用。

tl; dr:decltype(auto)并不总是与auto&&相同。

答案 2 :(得分:0)

auto&&始终是引用类型。另一方面,decltype(auto)可以是引用或值类型,具体取决于所使用的初始化程序。

由于ret语句中的return没有用括号括起来,因此call()的推导返回类型仅取决于声明类型 > entity ret表达式 ret值类别

template<typename Callable, typename... Args>
decltype(auto) call(Callable&& op, Args&&... args) {
   decltype(auto) ret{std::invoke(std::forward<Callable>(op),
                                  std::forward<Args>(args)...)};
   ...
   return ret;
}

如果Callable按值返回 ,则op的调用表达式的值类别将是 prvalue 。在这种情况下:

  • decltype(auto)将推论res作为非引用类型(即值类型)。
  • auto&&会得出res作为参考类型。

如上所述,decltype(auto)处的call()的返回类型与res的返回类型相同。因此,如果使用auto&&来推导res的类型而不是decltype(auto),则call()的返回类型将是对本地对象{{ 1}},在ret返回后不存在。

答案 3 :(得分:0)

我有一个类似的问题,但特定于如何正确返回ret,就像我们直接调用invoke而不是call一样。

在您显示的示例中,call(A, B)std::invoke(A, B)的{​​{1}} 没有具有相同的返回类型A

具体来说,当B返回invoke时,T&&返回call

您可以在此示例中看到它(wandbox link

T&

因此看来,正确转发函数返回值的唯一方法是这样做

#include <type_traits>
#include <iostream>

struct PlainReturn {
    template<class F, class Arg>
    decltype(auto) operator()(F&& f, Arg&& arg) {
        decltype(auto) ret = std::forward<F>(f)(std::forward<Arg>(arg));
        return ret;
    }
};

struct ForwardReturn {
    template<class F, class Arg>
    decltype(auto) operator()(F&& f, Arg&& arg) {
        decltype(auto) ret = std::forward<F>(f)(std::forward<Arg>(arg));
        return std::forward<decltype(ret)>(ret);
    }
};

struct IfConstexprReturn {
    template<class F, class Arg>
    decltype(auto) operator()(F&& f, Arg&& arg) {
        decltype(auto) ret = std::forward<F>(f)(std::forward<Arg>(arg));
        if constexpr(std::is_rvalue_reference_v<decltype(ret)>) {
            return std::move(ret);
        } else {
            return ret;
        }
    }
};

template<class Impl>
void test_impl(Impl impl) {
    static_assert(std::is_same_v<int, decltype(impl([](int) -> int {return 1;}, 1))>, "Should return int if F returns int");
    int i = 1;
    static_assert(std::is_same_v<int&, decltype(impl([](int& i) -> int& {return i;}, i))>, "Should return int& if F returns int&");
    static_assert(std::is_same_v<int&&, decltype(impl([](int&& i) -> int&& { return std::move(i);}, 1))>, "Should return int&& if F returns int&&");
}

int main() {
    test_impl(PlainReturn{}); // Third assert fails: returns int& instead
    test_impl(ForwardReturn{}); // First assert fails: returns int& instead
    test_impl(IfConstexprReturn{}); // Ok
}

这是一个陷阱(我掉进去发现了!)

返回decltype(auto) operator()(F&& f, Arg&& arg) { decltype(auto) ret = std::forward<F>(f)(std::forward<Arg>(arg)); if constexpr(std::is_rvalue_reference_v<decltype(ret)>) { return std::move(ret); } else { return ret; } } 的函数非常少见,很容易在一段时间内未被发现。