簡體   English   中英

使用 auto&& 完美轉發返回值

[英]Perfect-forwarding a return value with auto&&

考慮一下C++ 模板中的這句話:完整指南(第 2 版)

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

請注意,用auto&&聲明ret是不正確的。 作為參考, auto&&將返回值的生命周期延長到其范圍的末尾,但不會超出函數調用者的return語句

作者說auto&&不適合完美轉發返回值。 但是, decltype(auto)不也形成了對 xvalue/lvalue 的引用嗎? IMO, decltype(auto)然后遇到同樣的問題。 那么,作者的意義何在?

編輯:

上面的代碼片段應該放在這個函數模板中。

template<typename Callable, typename... Args>
decltype(auto) call(Callable&& op, Args&&... args) {
    // here
}

這里有兩個扣除。 一個來自 return 表達式,一個來自std::invoke表達式。 因為decltype(auto)被推導為未加括號的 id-expression 的聲明類型,我們可以專注於從std::invoke表達式推導。

引自[dcl.type.auto.deduct] 第 5 段:

如果占位符是decltype(auto)類型說明符,則T應單獨作為占位符。 T推導的類型如 [dcl.type.simple] 中所述確定,就好像edecltype的操作數一樣。

並引用自[dcl.type.simple] 第 4 段

對於表達式edecltype(e)表示的類型定義如下:

  • 如果e是命名結構化綁定 ([dcl.struct.bind]) 的未加括號的 id 表達式,則decltype(e)是結構化綁定聲明規范中給出的引用類型;

  • 否則,如果e是未加括號的id 表達式或未加括號的類成員訪問,則decltype(e)e命名的實體的類型。 如果沒有這樣的實體,或者如果e命名了一組重載函數,則程序是病式的;

  • 否則,如果e是一個 xvalue,則decltype(e)T&& ,其中Te的類型;

  • 否則,如果e是左值,則decltype(e)T& ,其中Te的類型;

  • 否則, decltype(e)e的類型。

注意decltype(e)被推導為T而不是T&&如果e是純右值。 這是與auto&&的區別。

所以如果std::invoke(std::forward<Callable>(op), std::forward<Args>(args)...)是純右值,例如Callable的返回類型不是引用,即按值返回, ret被推導為同類型而不是引用,完美轉發了按值返回的語義。

但是,decltype(auto) 不也形成了對 xvalue/lvalue 的引用嗎?

不。

decltype(auto)的部分魔力在於它知道ret是左值,因此它不會形成引用

如果您編寫了return (ret) ,它確實會解析為引用類型,並且您將返回對局部變量的引用。

tl;dr: decltype(auto)並不總是與auto&&相同。

auto&&始終是引用類型。 另一方面, decltype(auto)可以是引用類型或值類型,具體取決於所使用的初始化程序。

由於return語句中的ret沒有被括號包圍,因此call()的推導返回類型僅取決於實體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的調用表達式的值類別將為純右值 在這種情況下:

  • decltype(auto)會將res推斷為非引用類型(即值類型)。
  • auto&&會將res推斷為引用類型。

如上所述, call()的返回類型中的decltype(auto)只會導致與res相同的類型。 因此,如果使用auto&&而不是decltype(auto)來推斷res的類型,則call()的返回類型將是對本地對象ret的引用,該對象在call()返回后不存在。

我有一個類似的問題,但具體到如何正確返回ret就像我們直接invoke而不是call一樣。

在您顯示的示例中, call(A, B) , B) 對於每個AB沒有std::invoke(A, B)相同的返回類型。

具體來說,當invoke返回T&&時, call返回T&

你可以在這個例子中看到它( wandbox 鏈接

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

這是一個相當大的陷阱(我是掉進去才發現的)。

返回T&&的函數很少見,很容易在一段時間內不被發現。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM