Issue 2442: call_once() shouldn't DECAY_COPY() (original) (raw)
This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of C++17 status.
2442. call_once() shouldn't DECAY_COPY()
Section: 32.6.7.2 [thread.once.callonce] Status: C++17 Submitter: Stephan T. Lavavej Opened: 2014-10-01 Last modified: 2017-07-30
Priority: Not Prioritized
View all other issues in [thread.once.callonce].
View all issues with C++17 status.
Discussion:
When LWG 891(i) overhauled call_once()'s specification, it used decay_copy(), following LWG 929(i)'s overhaul of thread's constructor.
In thread's constructor, this is necessary and critically important. 32.4.3.3 [thread.thread.constr]/5 "The new thread of execution executes _INVOKE_(_DECAYCOPY_(std::forward<F>(f)), _DECAYCOPY_(std::forward<Args>(args))...)with the calls to _DECAYCOPY_ 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. Givenmeow(int&),meow(i)can be called directly, butcall_once(flag, meow, i)won't compile.decay_copy()moves from modifiable rvalues. Givenpurr(const unique_ptr<int>&),purr(move(up))won't modifyup. (This is observable, because moved-fromunique_ptrs are guaranteed empty.) However,call_once(flag, purr, move(up))will leaveupempty after the first active execution. Observe the behavioral difference — ifpurr()is directly called like this repeatedly until it doesn't throw an exception, each call will observeupunchanged. Withcall_once(), the second active execution will observeupto be empty.
call_once() should use perfect forwarding without decay_copy(), in order to avoid interfering with the call like this.
[2015-02 Cologne]
Handed over to SG1.
[2015-05 Lenexa, SG1 response]
Looks good to us, but this is really an LWG issue.
[2015-05-07 Lenexa: Move Immediate]
LWG 2442 call_once shouldn't decay_copy
STL summarizes the SG1 minutes.
Marshall: Jonathan updated all the issues with SG1 status last night. Except this one.
STL summarizes the issue.
Dietmar: Of course, call_once has become useless.
STL: With magic statics.
Jonathan: Magic statics can't be per object, which I use in future.
Marshall: I see why you are removing the MoveConstructible on the arguments, but what about Callable?
STL: That's a type named Callable, which we will no longer decay_copy. We're still requiring the INVOKE expression to be valid.
Marshall: Okay. Basically, ripping the decay_copy out of here.
STL: I recall searching the Standard for other occurrences and I believe this is the only inappropriate use of decay_copy.
Marshall: We do the decay_copy.
Jonathan: Us too.
Marshall: What do people think?
Jonathan: I think STL's right. In the use I was mentioning inside futures, I actually pass them by reference_wrapper and pointers, to avoid the decay causing problems. Inside the call_once, I then extract the args. So I've had to work around this and didn't realize it was a defect.
Marshall: What do people think is the right resolution?
STL: I would like to see Immediate.
Hwrd: No objections to Immediate.
Marshall: Bill is nodding.
PJP: He said it. Everything STL says applies to our other customers.
Marshall: Any objections to Immediate?
Jonathan: I can't see any funky implementations where a decay_copy would be necessary?
Marshall: 6 votes for Immediate, 0 opposed, 0 abstaining.
Proposed resolution:
This wording is relative to N3936.
- Change 32.6.7.2 [thread.once.callonce] p1+p2 as depicted:
template<class Callable, class ...Args>
void call_once(once_flag& flag, Callable&& func, Args&&... args);-1- Requires:
Callableand eachTiinArgsshall satisfy theMoveConstructiblerequirements._INVOKE_(~~_DECAYCOPY_(~~std::forward<Callable>(func)~~)~~, ~~_DECAYCOPY_(~~std::forward<Args>(args)~~)~~...)(20.9.2) shall be a valid expression.-2- Effects; […] An active execution shall call
_INVOKE_(~~_DECAYCOPY_(~~std::forward<Callable>(func)~~)~~, ~~_DECAYCOPY_(~~std::forward<Args>(args)~~)~~...). […]