12

別のスレッドで実行するラムダに完全に転送したい関数テンプレートがあります。以下は、直接コンパイルできる最小限のテスト ケースです。

#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を使用しています(おそらくそうではありません)

4

1 に答える 1

8

async私が理解している方法では、const 以外の左辺値参照を引数として期待する関数を使用することはできません。これasyncは、常にそれらのコピーを内部で作成する (または内部で移動する) ことにより、それらが存在し、スレッドの実行時間全体にわたって有効であることを保証するためです。作成した。

具体的には、標準は次のように述べていasync(launch policy, F&& f, Args&&... args)ます。

(§30.6.8)

(2) Requires:FそしてそれぞれTiArgsMoveConstructible 要件を満たさなければなりません。INVOKE(DECAY_COPY (std::forward<F>(f)), DECAY_COPY (std::forward<Args>(args))...)(20.8.2, 30.3.1.2) は有効な式でなければなりません。

(3) 効果: [...] policy & launch::async がゼロ以外の場合 — (20.8.2, 30.3.1.2) を呼び出してINVOKE(DECAY_COPY (std::forward<F>(f)),DECAY_COPY (std::forward<Args>(args))...)、スレッド オブジェクトによって表される実行の新しいスレッドであるかのように呼び出します。DECAY_COPY()async を呼び出したスレッドで評価されます。戻り値はすべて結果として共有状態に格納されます。INVOKE (DECAY_COPY (std::forward(f))、DECAY_COPY (std::forward(args))...) の実行から伝播された例外は、例外結果として共有状態に格納されます。
スレッド オブジェクトは共有状態に格納され、その状態を参照する非同期の戻りオブジェクトの動作に影響を与えます。

残念ながら、これは参照を に置き換えることさえできないことを意味しますstd::reference_wrapper。後者はムーブ構築可能ではないからです。参照の代わりにa を使用するstd::unique_ptrとうまくいくと思います(ただし、関数の引数は常にヒープ上にあることを意味します)。

(編集/訂正)上記の反対を主張しましたが、実際に回避策を可能にすること
に気付いたとき、私は関連する問題に取り組んでいました。std::reference_wrapper

std::reference_wrapper左辺値参照を aにラップするが、右辺値参照は変更しないままにする関数を定義する場合、T&&引数を に渡す前に、この関数を介して引数を渡すことができstd::asyncます。この特別なラッパー関数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、ラムダ関数を呼び出すときに自動的に左辺値参照に変換されます。

于 2012-12-11T05:48:07.467 に答える