visual studio implementation of “move semantics” and “rvalue reference”

后端 未结 2 434
终归单人心
终归单人心 2020-12-10 19:14

I came across a Youtube video on c++11 concurrency (part 3) and the following code, which compiles and generates correct result in the video.

However, I got a compi

相关标签:
2条回答
  • 2020-12-10 19:34

    This appears to be a bug in MSVC2012. (and on quick inspection, MSVC2013 and MSVC2015)

    thread does not use perfect forwarding directly, as storing a reference to data (temporary or not) in the originating thread and using it in the spawned thread would be extremely error prone and dangerous.

    Instead, it copies each argument into decay_t<?>'s internal data.

    The bug is that when it calls the worker function, it simply passes that internal copy to your procedure. Instead, it should move that internal data into the call.

    This does not seem to be fixed in compiler version 19, which I think is MSVC2015 (did not double check), based off compiling your code over here

    This is both due to the wording of the standard (it is supposed to invoke a decay_t<F> with decay_t<Ts>... -- which means rvalue binding, not lvalue binding), and because the local data stored in the thread will never be used again after the invocation of your procedure (so logically it should be treated as expiring data, not persistent data).

    Here is a work around:

    template<class F>
    struct thread_rvalue_fix_wrapper {
      F f;
      template<class...Args>
      auto operator()(Args&...args)
      -> typename std::result_of<F(Args...)>::type
      {
          return std::move(f)( std::move(args)... );
      }
    };
    template<class F>
    thread_rvalue_fix_wrapper< typename std::decay<F>::type >
    thread_rvalue_fix( F&& f ) { return {std::forward<F>(f)}; }
    

    then

    thread th(thread_rvalue_fix(&toSin), /*std::ref(list)*/std::move(list));
    

    should work. (tested in MSVC2015 online compiler linked above) Based off personal experience, it should also work in MSVC2013. I don't know about MSVC2012.

    0 讨论(0)
  • 2020-12-10 19:53

    What is returned from std::move is indeed an rvalue reference, but that doesn't matter because the thread constructor does not use perfect forwarding for its arguments. First it copies/moves them to storage owned by the new thread. Then, inside the new thread, the supplied function is called using the copies.

    Since the copies are not temporary objects, this step won't bind to rvalue-reference parameters.

    What the Standard says (30.3.1.2):

    The new thread of execution executes

    INVOKE( DECAY_COPY(std::forward<F>(f)),  DECAY_COPY(std::forward<Args>(args))... )
    

    with the calls to DECAY_COPY being evaluated in the constructing thread.

    and

    In several places in this Clause the operation DECAY_COPY(x) is used. All such uses mean call the function decay_copy(x) and use the result, where decay_copy is defined as follows:

    template <class T> decay_t<T> decay_copy(T&& v)
    { return std::forward<T>(v); }
    

    The value category is lost.

    0 讨论(0)
提交回复
热议问题