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: 33.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 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. 33.4.3.3 [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_ptr`s 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.

[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.

1. Change 33.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: `Callable` and each `Ti` in `Args` shall satisfy the `MoveConstructible` requirements. `INVOKE(DECAY_COPY(std::forward<Callable>(func)), DECAY_COPY(std::forward<Args>(args))...)` (20.9.2) shall be a valid expression.

   > > -2- Effects; […] An active execution shall call `INVOKE(DECAY_COPY(std::forward<Callable>(func)), DECAY_COPY(std::forward<Args>(args))...)`. […]