Date
2014-10-01.00:00:00
Message id
7142

Content

When LWG 891 overhauled call_once()'s specification, it used decay_copy(), following LWG 929's overhaul of thread's constructor.

In thread's constructor, this is necessary and critically important. [thread.thread.constr]/5 "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." requires the parent thread to copy arguments for the child thread to access.

In call_once(), this is unnecessary and harmful. It's unnecessary because call_once() doesn't transfer arguments between threads. It's harmful because:

  • decay_copy() returns a prvalue. Given meow(int&), meow(i) can be called directly, but call_once(flag, meow, i) won't compile.

  • decay_copy() moves from modifiable rvalues. Given purr(const unique_ptr<int>&), purr(move(up)) won't modify up. (This is observable, because moved-from unique_ptrs are guaranteed empty.) However, call_once(flag, purr, move(up)) will leave up empty after the first active execution. Observe the behavioral difference — if purr() is directly called like this repeatedly until it doesn't throw an exception, each call will observe up unchanged. With call_once(), the second active execution will observe up to be empty.

call_once() should use perfect forwarding without decay_copy(), in order to avoid interfering with the call like this.