2012-12-11 41 views
11

我有一個函數模板,我想完美轉發到一個lambda,我在另一個線程上運行。這裏是你可以直接編譯一個最小的測試用例:完美轉發到異步lambda

#include <thread> 
#include <future> 
#include <utility> 
#include <iostream> 
#include <vector> 

/** 
* Function template that does perfect forwarding to a lambda inside an 
* async call (or at least tries to). I want both instantiations of the 
* function to work (one for lvalue references T&, and rvalue reference T&&). 
* However, I cannot get the code to compile when calling it with an lvalue. 
* See main() below. 
*/ 
template <typename T> 
std::string accessValueAsync(T&& obj) 
{ 

    std::future<std::string> fut = 
     std::async(std::launch::async, 
      [](T&& vec) mutable 
      { 
       return vec[0]; 
      }, 
      std::forward<T>(obj)); 

    return fut.get(); 
} 

int main(int argc, char const *argv[]) 
{ 
    std::vector<std::string> lvalue{"Testing"}; 

    // calling with what I assume is an lvalue reference does NOT compile 
    std::cout << accessValueAsync(lvalue) << std::endl; 

    // calling with rvalue reference compiles 
    std::cout << accessValueAsync(std::move(lvalue)) << std::endl; 

    // I want both to compile. 

    return 0; 
} 

對於非編譯的情況下,這裏是錯誤消息是可理解的最後一行:我有一種感覺

main.cpp|13 col 29| note: no known conversion for argument 1 from ‘std::vector<std::basic_string<char> >’ to ‘std::vector<std::basic_string<char> >&’ 

它可能與如何推斷T&&有關,但我無法確定故障的確切位置並修復它。有什麼建議麼?

謝謝!

編輯:我用gcc 4.7.0以防萬一這可能是一個編譯器問題(可能不是)

+0

我不確定自己是否正確,但也許C++要求您明確地*在具有複製和移動語義的對象上使用'std :: move'。 –

+0

對不起,我沒有很好地說出這個問題。讓我編輯。簡而言之,我希望這兩個函數實例都能正常工作,並且它們每個都做不同的事情(一個通過傳遞引用來轉發向量,另一個通過每次移動右值引用來向前)。 –

+0

原來,如果你轉換成'(const std :: vector &)',它就可以工作。 –

回答

7

就我理解你不能通過async使用一個功能,預計非const左值作爲參考參數,因爲async將始終在內部複製它們(或將它們移到裏面)以確保它們存在並且在創建線程的整個運行時間內都有效。

具體而言,標準說,大約async(launch policy, F&& f, Args&&... args)

(§30.6.8)

(2)需要:FArgs每個Ti應滿足MoveConstructible要求。 INVOKE(DECAY_COPY (std::forward<F>(f)), DECAY_COPY (std::forward<Args>(args))...)(20.8.2,30.3.1.2)應是一個有效的表達式。 (3)效果:[...]如果策略& launch :: async非零 - 調用INVOKE(DECAY_COPY (std::forward<F>(f)),DECAY_COPY (std::forward<Args>(args))...)(20.8.2,30.3.1.2),就好像在由線程對象表示的新執行線程中一樣在調用異步的線程中對DECAY_COPY()的調用進行評估。任何返回值都作爲結果存儲在共享狀態中。從執行INVOKE(DECAY_COPY(std :: forward(f)),DECAY_COPY(std :: forward(args))...)開始傳播的任何異常作爲異常結果存儲在共享狀態中。
線程對象以共享狀態存儲,並影響引用該狀態的任何異步的返回對象的行爲。

不幸的是,這意味着你甚至不能用std::reference_wrapper代替引用,因爲後者不是可移動構造的。我想使用std::unique_ptr而不是參考將工作(但意味着,你的函數參數將始終生活在堆上)。

(EDIT /校正)
我被上一個相關的問題的工作當我意識到std::reference_wrapper實際上使一種解決方法,雖然我權利上述相反。

如果你定義一個包裝在std::reference_wrapper左值引用,但保留右值引用不變的功能,你可以移交到std::async之前通過該功能,T&&說法。我呼籲低於這個特殊的包裝函數wrap_lval

#include <thread> 
#include <future> 
#include <utility> 
#include <iostream> 
#include <vector> 
#include <type_traits> 

/* First the two definitions of wrap_lval (one for rvalue references, 
    the other for lvalue references). */ 

template <typename T> 
constexpr T&& 
wrap_lval(typename std::remove_reference<T>::type &&obj) noexcept 
{ return static_cast<T&&>(obj); } 

template <typename T> 
constexpr std::reference_wrapper<typename std::remove_reference<T>::type> 
wrap_lval(typename std::remove_reference<T>::type &obj) noexcept 
{ return std::ref(obj); } 


/* The following is your code, except for one change. */ 
template <typename T> 
std::string accessValueAsync(T&& obj) 
{ 

    std::future<std::string> fut = 
    std::async(std::launch::async, 
      [](T&& vec) mutable 
      { 
      return vec[0]; 
      }, 
      wrap_lval<T>(std::forward<T>(obj))); // <== Passing obj through wrap_lval 

    return fut.get(); 
} 

int main(int argc, char const *argv[]) 
{ 
    std::vector<std::string> lvalue{"Testing"}; 

    std::cout << accessValueAsync(lvalue) << std::endl; 

    std::cout << accessValueAsync(std::move(lvalue)) << std::endl; 

    return 0; 
} 

隨着這一變化,都調用accessValueAsync編譯和工作。第一個使用左值引用的自動將它包裝在std::reference_wrapper中。當std::async調用lambda函數時,後者會自動轉換回左值引用。

+0

謝謝,有道理。我想衰變副本確保引用不會通過,並將其「防禦性」複製。 –