2012-12-11 10 views
11

私は、別のスレッドで実行するラムダへの完全な転送をしたい機能テンプレートがあります。ここでは、直接コンパイルすることができ、最小限のテストケースである:非同期ラムダへの完璧な転送

#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

申し訳ありませんが、私は文句をよく言いませんでした。私を編集させてください。その短所は、私は両方の関数のインスタンス化を働かせたいと思っており、それぞれは別のことをしているということです(参照を渡してベクトルを転送し、他の前方はrvalue参照を毎回動かすことによってベクトルを転送します)。 –

+0

'(const std :: vector &)'にキャストすると動作することが分かります。 –

答えて

7

非const左辺値参照として期待し、私はあなたがasyncを通じて機能を使用することはできませんそれを理解する方法なぜなら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 /訂正)
Iはstd::reference_wrapperが実際に回避策を可能にすることに気づいたとき、私は上記の逆の記載が、私は、関連する問題に取り組んでいました。

あなたはstd::reference_wrapperで左辺値参照をラップしますが、そのまま右辺値参照を残し関数を定義した場合、あなたはstd::asyncにそれを引き渡す前に、この機能によってT&&引数を渡すことができます。この変更に伴い

#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コンパイルと仕事の両方の呼び出し:私は、以下のこの特別なラッパー関数wrap_lvalと呼ばれています。左辺参照を使用する最初のものは、自動的にstd::reference_wrapperにラップされます。後者は、std::asyncがラムダ関数を呼び出すと自動的に左辺値参照に変換されます。

+0

ありがとうございます。私は、腐敗コピーは参照が通過しないことを確かめ、それらを「防衛的に」コピーします。 –

関連する問題