Section: 32.4.3.3 [thread.thread.constr] Status: C++11 Submitter: Anthony Williams Opened: 2008-10-23 Last modified: 2016-01-28
Priority: Not Prioritized
View all other issues in [thread.thread.constr].
View all issues with C++11 status.
Discussion:
Addresses UK 323
The thread
constructor for starting a new thread with a function and
arguments is overly constrained by the signature requiring rvalue
references for func
and args
and the CopyConstructible
requirements
for the elements of args
. The use of an rvalue reference for the
function restricts the potential use of a plain function name, since
the type of the bound parameter will be deduced to be a function
reference and decay to pointer-to-function will not happen. This
therefore complicates the implementation in order to handle a simple
case. Furthermore, the use of rvalue references for args prevents the
array to pointer decay. Since arrays are not CopyConstructible
or even
MoveConstructible
, this essentially prevents the passing of arrays as
parameters. In particular it prevents the passing of string literals.
Consequently a simple case such as
void f(const char*); std::thread t(f,"hello");
is ill-formed since the type of the string literal is const char[6]
.
By changing the signature to take all parameters by value we can
eliminate the CopyConstructible
requirement and permit the use of
arrays, as the parameter passing semantics will cause the necessary
array-to-pointer decay. They will also cause the function name to
decay to a pointer to function and allow the implementation to handle
functions and function objects identically.
The new signature of the thread
constructor for a function and
arguments is thus:
template<typename F,typename... Args> thread(F,Args... args);
Since the parameter pack Args
can be empty, the single-parameter
constructor that takes just a function by value is now redundant.
[ Howard adds: ]
I agree with everything Anthony says in this issue. However I believe we can optimize in such a way as to get the pass-by-value behavior with the pass-by-rvalue-ref performance. The performance difference is that the latter removes a
move
when passing in an lvalue.This circumstance is very analogous to
make_pair
(22.3 [pairs]) where we started with passing by const reference, changed to pass by value to get pointer decay, and then changed to pass by rvalue reference, but modified withdecay<T>
to retain the pass-by-value behavior. If we were to apply the same solution here it would look like:template <class F> explicit thread(F f);template <class F, class ...Args> thread(F&& f, Args&&... args);-4- Requires:
F
and eachTi
inArgs
shall beCopyConstructible
if an lvalue and otherwiseMoveConstructible
.INVOKE(f, w1, w2, ..., wN)
(22.10.4 [func.require]) shall be a valid expression for some valuesw1, w2, ... , wN,
whereN == sizeof...(Args)
.-5- Effects: Constructs an object of type
thread
and executes. Constructs the following objects in memory which is accessible to a new thread of execution as if:INVOKE(f, t1, t2, ..., tN)
in a new thread of execution, wheret1, t2, ..., tN
are the values inargs...
typename decay<F>::type g(std::forward<F>(f)); tuple<typename decay<Args>::type...> w(std::forward<Args>(args)...);The new thread of execution executes
INVOKE(g, wi...)
where thewi...
refers to the elements stored in thetuple w
. Any return value fromg
is ignored.IfIf the evaluation off
terminates with an uncaught exception,std::terminate()
shall be called.INVOKE(g, wi...)
terminates with an uncaught exception,std::terminate()
shall be called [Note:std::terminate()
could be called before enteringg
. -- end note]. Any exception thrown before the evaluation ofINVOKE
has started shall be catchable in the calling thread.Text referring to when
terminate()
is called was contributed by Ganesh.
[ Batavia (2009-05): ]
We agree with the proposed resolution, but would like the final sentence to be reworded since "catchable" is not a term of art (and is used nowhere else).
[ 2009-07 Frankfurt: ]
This is linked to N2901.
Howard to open a separate issue to remove (1176).
In Frankfurt there is no consensus for removing the variadic constructor.
[ 2009-10 Santa Cruz: ]
We want to move forward with this issue. If we later take it out via 1176 then that's ok too. Needs small group to improve wording.
[ 2009-10 Santa Cruz: ]
Stefanus provided revised wording. Moved to Review Here is the original wording:
Modify the class definition of
std::thread
in 32.4.3 [thread.thread.class] to remove the following signature:template<class F> explicit thread(F f);template<class F, class ... Args> explicit thread(F&& f, Args&& ... args);Modify 32.4.3.3 [thread.thread.constr] to replace the constructors prior to paragraph 4 with the single constructor as above. Replace paragraph 4 - 6 with the following:
-4- Requires:
F
and eachTi
inArgs
shall beCopyConstructible
if an lvalue and otherwiseMoveConstructible
.INVOKE(f, w1, w2, ..., wN)
(22.10.4 [func.require]) shall be a valid expression for some valuesw1, w2, ... , wN,
whereN == sizeof...(Args)
.-5- Effects: Constructs an object of type
thread
and executes. Constructs the following objects:INVOKE(f, t1, t2, ..., tN)
in a new thread of execution, wheret1, t2, ..., tN
are the values inargs...
typename decay<F>::type g(std::forward<F>(f)); tuple<typename decay<Args>::type...> w(std::forward<Args>(args)...);and executes
INVOKE(g, wi...)
in a new thread of execution. These objects shall be destroyed when the new thread of execution completes. Any return value fromg
is ignored.IfIf the evaluation off
terminates with an uncaught exception,std::terminate()
shall be called.INVOKE(g, wi...)
terminates with an uncaught exception,std::terminate()
shall be called [Note:std::terminate()
could be called before enteringg
. -- end note]. Any exception thrown before the evaluation ofINVOKE
has started shall be catchable in the calling thread.-6- Synchronization: The invocation of the constructor happens before the invocation of
f
g
.
[ 2010-01-19 Moved to Tentatively Ready after 5 positive votes on c++std-lib. ]
Proposed resolution:
Modify the class definition of std::thread
in 32.4.3 [thread.thread.class] to remove the
following signature:
template<class F> explicit thread(F f);template<class F, class ... Args> explicit thread(F&& f, Args&& ... args);
Modify 32.4.3.3 [thread.thread.constr] to replace the constructors prior to paragraph 4 with the single constructor as above. Replace paragraph 4 - 6 with the following:
Given a function as follows:
template<typename T> typename decay<T>::type decay_copy(T&& v) { return std::forward<T>(v); }-4- Requires:
F
and eachTi
inArgs
shallbesatisfy theCopyConstructible
if an lvalue and otherwiseMoveConstructible
requirements.INVOKE(f, w1, w2, ..., wN)
(22.10.4 [func.require]) shall be a valid expression for some valuesw1, w2, ... , wN,
whereN == sizeof...(Args)
.INVOKE(decay_copy(std::forward<F>(f)), decay_copy(std::forward<Args>(args))...)
(22.10.4 [func.require]) shall be a valid expression.-5- Effects: Constructs an object of type
thread
and executesThe new thread of execution executesINVOKE(f, t1, t2, ..., tN)
in a new thread of execution, wheret1, t2, ..., tN
are the values inargs...
. Any return value fromf
is ignored. Iff
terminates with an uncaught exception,std::terminate()
shall be called.INVOKE(decay_copy(std::forward<F>(f)), decay_copy(std::forward<Args>(args))...)
with the calls todecay_copy()
being evaluated in the constructing thread. Any return value from this invocation is ignored. [Note: this implies any exceptions not thrown from the invocation of the copy off
will be thrown in the constructing thread, not the new thread. — end note]. If the invocation ofINVOKE(decay_copy(std::forward<F>(f)), decay_copy(std::forward<Args>(args))...)
terminates with an uncaught exception,std::terminate
shall be called.-6- Synchronization: The invocation of the constructor happens before the invocation of the copy of
f
.