3

考慮構建類存儲函數的以下代碼。我想要這些函數被完美地轉發(不管它們是否是函數指針,函數,lambda表達式......)。但我不完全理解std::forward和普遍引用背後發生的所有類型扣除。在上面的代碼,我有三個問題:功能的完美轉發以構建函數列表類

  • 應該_fstd::tuple<F...>型或std::tuple<F&&...>
  • 是否可以推斷出模板參數列表中的返回類型R(因爲這樣做(爲什麼?)手動的,而不是auto/decltype(auto)將有助於瞭解正在發生的事情)
  • 在製造商中,function_list模板參數應該是什麼?decltype(std::forward<F>(f)...)F,或F&&...(爲什麼)

注意:function_list的構造函數並不是直接調用,而是make_function_list正在完成這項工作。

編輯: 當這個案件的operator()(這裏沒有顯示)不被保證在同一個陳述上被調用時,這種情況是否安全?

template <class... F> 
constexpr function_list<F...> make_function_list(F&&... f) 
{ 
    return function_list<F&&...>(std::forward<F>(f)...); 
} 
+0

你幾乎肯定會** **不希望他們是'˚F&&'。 – SergeyA

+0

1)'tuple '2)您不需要模板列表中的'R',您已經知道返回類型是'function_list '3)'F &&',因爲它是一個轉發引用,它將rvalue和左值(你說你想要完美的轉發,所以這就是你如何得到它) – AndyG

+1

如果你可以保證函數調用操作符在與'make_function_list'相同的語句中被調用,你可以使用'F &&'(你可以在operator()') –

回答

3

但我並不完全理解std::forward和普遍引用背後發生的所有類型演繹。

通過一個例子很容易理解。

template <typename T> 
void f(T&&) 
{ 
    std::tuple<T>{}; // (0) 
    std::tuple<T&&>{}; // (1) 
} 

在的情況下(0)

  • T推導作爲T右值
  • T推導作爲T&左值

(1)的情況下:

  • T推導作爲T&&右值
  • T推導作爲T&左值

正如你所看到的,兩者之間唯一的區別在於如何推導出rvalues

關於std::forward,這是它做什麼:

template <typename T> 
void g(T&&); 

template <typename T> 
void f(T&& x) 
{ 
    g(x) // (0) 
    g(std::forward<T>(x)); // (1) 
} 

在的情況下(0)

  • x始終是一個左值

(1)的情況下:如果T推導作爲T

  • x澆鑄到T&&

  • x保持一個左值否則。

std::forward基本上保留通過觀察T是如何推導的x車型範疇。


應_F是std::tuple<F...>型或std::tuple<F&&...>

我認爲,你的情況應該是std::tuple<F...>,只要你想將任左值引用的。

std::tuple<F&&...>將存儲要麼左值引用右值引用 - 這將導致臨時對象的情況下懸掛引用


是否可以推斷返回類型R在模板參數列表

是的,它只是function_list<F...>

template <class... F, class R = function_list<F...>> 
constexpr R make_function_list(F&&... f) 
{ 
    return function_list<F...>(std::forward<F>(f)...); 
} 

你甚至不需要R模板參數。

template <class... F> 
constexpr function_list<F...> make_function_list(F&&... f) 
{ 
    return function_list<F...>(std::forward<F>(f)...); 
} 

在製造商中,function_list模板參數應該是什麼:decltype(std::forward<F>(f)...)F,或F&&...

function_list應採取F...如在列出的原因模板參數這個答案的開頭(即避免對臨時參數的懸掛參考)

它仍然應該採取std::forward<F>(f)...作爲參數,以允許右值轉發這樣(即移動到右值function_list的元組)

2

如果他們F&&,然後如果你穿越到臨時到make_function_list,包含tuple將存儲的右值引用到臨時傳遞給make_function_list返回的類。

在下一行,它現在是一個懸而未決的參考。

這在大多數使用情況下似乎不好。這在全部用例中實際上並不壞; forward_as_tuple這樣做。但是這樣的用例是而不是的一般用例。該圖案非常脆弱和危險。

一般而言,如果您要退回T&&,則要將其作爲T返回。這可能導致對象的副本;但另一種選擇是跳入參考地獄。

這給我們:

template<class... Fs> 
struct function_list { 
    template<class... Gs> 
    explicit constexpr function_list(Gs&&... gs) noexcept 
    : fs(std::forward<Gs>(gs)...) 
    {} 
    std::tuple<Fs...> fs; 
}; 
template<class... Fs, class R = function_list<Fs...>> 
constexpr R make_function_list(Fs&&... fs) { 
    return R(std::forward<Fs>(fs)...); 
} 

也使function_list的男星explicit,因爲在1個論證情況下,轉予相當貪婪的隱式轉換構造函數。這是可以解決的,但需要花費更多的努力。

operator()需要一個實例。類型名稱不是實例。

0

這取決於function_list的用途。目前主要有兩種情況:

  1. function_list是一個臨時的輔助函數不應該活得比它出現在語句這裏我們可以存儲功能,他們每個人的完美,轉發給調用點參考:

    template <class... F> 
    struct function_list 
    { 
        std::tuple<F&&...> f_; 
    
        // no need to make this template 
        constexpr function_list(F&&... f) noexcept 
         : f_{std::forward<F>(f)...} 
        {} 
    
        template <std::size_t i, typename... A> 
        decltype(auto) call_at(A&&... a) 
        { 
         return std::invoke(std::get<i>(f_), std::forward<A>(a)...); 
        } 
    }; 
    
  2. function_list是一個包裝/容器對象類似於std::bind,在這種情況下,你要存儲的功能,腐朽的副本,以避免在此背景下晃來晃去的參考和完善的轉發就意味着轉發功能的構造他們的腐爛版本在f_然後在通話的灌輸腐朽功能與function_list本身的價值範疇的觀點:

    template <class... F> 
    struct function_list 
    { 
        std::tuple<std::decay_t<F>...> f_; 
    
        template <typename... G> 
        constexpr function_list(G&&... g) 
         : f_{std::forward<G>(g)...} 
        {} 
    
        template <std::size_t i, typename... A> 
        decltype(auto) call_at(A&&... a) & 
        { 
         return std::invoke(std::get<i>(f_), std::forward<A>(a)...); 
        } 
    
        template <std::size_t i, typename... A> 
        decltype(auto) call_at(A&&... a) const& 
        { 
         return std::invoke(std::get<i>(f_), std::forward<A>(a)...); 
        } 
    
        template <std::size_t i, typename... A> 
        decltype(auto) call_at(A&&... a) && 
        { 
         return std::invoke(std::get<i>(std::move(f_)), std::forward<A>(a)...); 
        } 
    
        template <std::size_t i, typename... A> 
        decltype(auto) call_at(A&&... a) const&& 
        { 
         return std::invoke(std::get<i>(std::move(f_)), std::forward<A>(a)...); 
        } 
    }; 
    

    std::bind,如果你真的想存儲的引用,你必須用std::reference_wrapper明確地這樣做。

建設是在兩種情況下是相同的:

template <class... F> 
constexpr auto make_function_list(F&&... f) 
{ 
    return function_list<F...>(std::forward<F>(f)...); 
}