Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Argument passing semantics for threads are, um, *counterintuitive*

14 views
Skip to first unread message

Scott Meyers

unread,
Jun 30, 2009, 11:29:27 AM6/30/09
to
Consider:

void increment(int& i) { ++i; }

increment(10); // error -- can't bind rvalue to lref
int x = 0;
increment(x);
assert(x == 1); // succeeds

No surprises. Now consider the exact same code launched in a separate thread:

std::thread t1(increment, 10); // compiles (see below)

std::thread t2(increment, x);
t2.join();
assert(x == 1); // fails (see below)

There are two problems here:
- Passing an rvalue to a non-const lref param within a thread is an
error, but doing what appears to be exactly the same thing in a new
thread is not an error.
- Passing an lvalue to a non-const lref param within a thread allows
the lvalue to be modified, but doing what appears to be exactly the
same thing in a new thread does not allow the lvalue to be modified.

The cause in both cases is that the std::thread constructor is defined
to make copies of each of its arguments before passing them on to the
thread function. Furthermore, the copies are (from what I can tell)
lvalues, so this has the effect of turning what are rvalues to the
caller into lvalues to the callee. I find these semantics to be
counterintuitive, but to be honest, I think a better word for them is
simply "insane." From the perspective of argument-passing, it seems
"obvious" to me that

f(x, y, z);

should behave identically to

std::thread(f, x, y, z);

How do others feel about the fact that argument passing across threads
has fundamentally different semantics from argument passing within a
thread?

Scott

--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std...@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]

Dragan Milenkovic

unread,
Jun 30, 2009, 6:59:05 PM6/30/09
to

I think that WP states that the thread constructor does perfect
forwarding of it's arguments to the function.

Here is what I tried:

#include <cassert>
#include <utility>

void increment(int& i) { ++i; }

class Foo {
public:
template <typename F, typename ... Args>
explicit Foo(F && f, Args && ... args) {
f(std::forward<Args>(args)...);
}
};

int main() {
//increment(10); // ERROR


int x = 0;
increment(x);

assert(x == 1); // OK

//Foo t1(increment, 10); // ERROR :-)

Foo t2(increment, x);
assert(x == 2); // OK
}

This is with gcc 4.4 :-)

Anyway, I do agree with you that they should behave identically.

And I still hate reference-collapsing rules...

--
Dragan

SG

unread,
Jun 30, 2009, 7:57:12 PM6/30/09
to
On 30 Jun., 17:29, Scott Meyers <use...@aristeia.com> wrote:
>
> void increment(int& i) { ++i; }
>
> increment(10); // error -- can't bind rvalue to lref
> int x = 0;
> increment(x);
> assert(x == 1); // succeeds
>
> No surprises. Now consider the exact same code launched in a separate thread:
>
> std::thread t1(increment, 10); // compiles (see below)
>
> std::thread t2(increment, x);
> t2.join();
> assert(x == 1); // fails (see below)
>
> There are two problems here:
> - Passing an rvalue to a non-const lref param within a thread is an
> error, but doing what appears to be exactly the same thing in a new
> thread is not an error.

I think the current draft is not so clear about the exact
requirements. This applies to std::bind as well. In my opinion the
ideal would be to specify the std::bind function template and
std::thread's constructor in a way so we get the following behaviour:

std::thread t1(increment, 10); // ill-formed #1
std::thread t2(increment, x); // ill-formed #2

int z = 0;
std::thread t3(increment, ref(z)); // OK #3
t3.join();
assert(z == 1); // OK

> f(x, y, z);
> should behave identically to
> std::thread(f, x, y, z);
>
> How do others feel about the fact that argument passing across threads
> has fundamentally different semantics from argument passing within a
> thread?

I think it's dangerous to "capture lvalues by reference". What if you
really want the thread to work on a copy? You'd have to explicitly
create a temporary rvalue and pass it to std::thread's constructor.

I personally prefer a std::thread constructor that behaves like
std::bind where every parameter is bound. If I really want to pass a
reference to an object to the thread I can use a reference wrapper.

To make #1 and #2 ill-formed and #3 well-formed we need a Callable-
like requirement of the form

template<typename F, typename... Args>
requires rr=RemoveReference<Args> ...
&& bf=BindersForward<rr::type> ...
&& Callable<F,bf::forward_as ...>
thread(F func, Args&&... args)

where BindersForward<T>::forward_as is the argument type used for
forwarding the bound parameter to the "inner function". It's a
reference-to-const regardless of whether T is an lvalue reference
(argument was an lvalue) or not a reference (argument was an rvalue).
Only in case T is a reference_wrapper<U> we can use U& as forward_as
and forward the result of reference_wrapper<U>::get(). Such a concept
could be defined like this:

auto concept BindersForward<typename ParamType> {
typename forward_as = ParamType const &;
forward_as forward(ParamType& x) {return x;}
}

// concept map for reference wrappers
template<typename T>
concept_map BindersForward<reference_wrapper<T>> {
typedef T& forward_as;
forward_as forward(reference_wrapper<T>& x) {return x.get();}
}

However, the ability to pass move-only types is also desirable. In
case of std::thread which invokes its functor exactly once (one shot)
this would be possible by using a special "BindersForwardOneShot"
concept where forward_as is an rvalue reference instead of a reference-
to-const. Obviously this may destroy the "bound arguments" which is
why it's only good enough for "one shot".

my two cents,
SG

Scott Meyers

unread,
Jul 1, 2009, 4:36:44 AM7/1/09
to
Scott Meyers wrote:
> Consider:
>
> void increment(int& i) { ++i; }
>
> increment(10); // error -- can't bind rvalue to lref
> int x = 0;
> increment(x);
> assert(x == 1); // succeeds
>
> No surprises. Now consider the exact same code launched in a separate
> thread:
>
> std::thread t1(increment, 10); // compiles (see below)
>
> std::thread t2(increment, x);
> t2.join();
> assert(x == 1); // fails (see below)

Apologies for following up to my own post, but I think I may have just had an
"Aha!" moment. Maybe the way to say "make the argument-passing semantics of
this cross-thread call the same as they'd be for a within-thread call" is to use
a lambda (untested code):

std::thread t1([]{ increment(10); }); // error -- 10 an rvalue
std::thread t2([&]{ increment(x); }); // fine, increments x

This would also address the fact that there is no packaged_task constructor
taking a function and a set of arguments (more untested code)

std::packaged_task<void(int&)> t(increment, 10); // error!
std::packaged_task<void(int&)> t(increment, x); // error!

but a lambda makes it possible (yet more untested code, because I'm thinking out
loud):

std::packaged_task<void()> t([]{ increment(10); ); // okay (I think)
std::packaged_task<void()> t([&]{ increment(x); ); // okay (I think)

Is it safe to say that if I want the argument-passing syntax and semantics of a
within-thread call, but I want to have the invoked function run on another
thread, I probably want to use a lambda as the function to pass to the thread
constructor, the packaged_task constructor, etc.?

Thanks,

Scott Meyers

unread,
Jul 1, 2009, 4:37:14 AM7/1/09
to
SG wrote:
> I think the current draft is not so clear about the exact
> requirements. This applies to std::bind as well. In my opinion the
> ideal would be to specify the std::bind function template and
> std::thread's constructor in a way so we get the following behaviour:
>
> std::thread t1(increment, 10); // ill-formed #1
> std::thread t2(increment, x); // ill-formed #2

Why should this be ill-formed? What's wrong with passing an lvalue by reference?

> I think it's dangerous to "capture lvalues by reference". What if you
> really want the thread to work on a copy? You'd have to explicitly
> create a temporary rvalue and pass it to std::thread's constructor.

No, you'd have to explicitly create a separate lvalue and pass it to thread's
constructor -- exactly what you'd have to do in the single-threaded case. (You
can't -- or shouldn't -- be able to pass an rvalue, because we're talking about
binding to an lref-to-non-const.)

> I personally prefer a std::thread constructor that behaves like
> std::bind where every parameter is bound. If I really want to pass a
> reference to an object to the thread I can use a reference wrapper.

Why do you prefer bind-compatible behavior here instead of
function-call-compatible behavior? Note that nothing is being bound. There is
no need for any special temporaries to be created. Parameters passed to the new
thread can be stored in registers or put on the (thread-specific) stack, just
as with the single-threaded case. Why should a developer expect that invoking a
function on the current thread's stack should behave differently (for
argument-passing purposes) than invoking the same function on the same arguments
to be run on a different thread's stack?

Scott

SG

unread,
Jul 1, 2009, 11:58:53 AM7/1/09
to
Dragan Milenkovic wrote:
>
> I think that WP states that the thread constructor does perfect
> forwarding of it's arguments to the function.

I don't think the "typical perfect forwarding" is possible. The
arguments have to be stored somewhere somehow. You could try to store
references only (to lvalues and rvalues) but that would be very bad in
case you pass a temporary object to it or something that changes its
state quickly and ceases to exist (like a counter of a for-loop) since
the constructor possibly returns /before/ the function has finished
executing so it may work with dangling references. std::thread's
constructor behaves more like a binder -- or at least it should which
implies copying the arguments and forwarding the copies with a
reference_wrapper-aware technique.

Cheers!
SG

--

SG

unread,
Jul 1, 2009, 1:19:49 PM7/1/09
to
Scott Meyers wrote:
> SG wrote:
>
> > [...] In my opinion the

> > ideal would be to specify the std::bind function template and
> > std::thread's constructor in a way so we get the following
> > behaviour:

void increment(int&);
....
int x=0;

> > std::thread t1(increment, 10); // ill-formed #1
> > std::thread t2(increment, x); // ill-formed #2
>
> Why should this be ill-formed? What's wrong with passing an
> lvalue by reference?

It's "wrong" (or better undesirable) because the thread works
asynchronously and may be stuck with dangling references. You
basically have three choices:

#1 bind lvalue and rvalue references (no copying whatsoever)
It is doomed to fail. How would I make the thread work on its
own copy? I can't. I'm stuck with creating a copy manually in
the invoking thread and have to make sure it stays alive long
enough before I can destroy it. Of course, this it not going
to work with temporary rvalues:

std::thread t1 ( []{foo();} );

The lambda object is bount to an rvalue reference. This
reference is passed to another thread. The execution of the
main thread continues and immediately destroys the temporary
lambda object. ==> This is a totally broken design. We DON'T
want perfect forwarding here.

#2 bind lvalues via reference and copy rvalues. It's not as bad as
the first option since rvalues are copied and temporaries will
just be copied and live long enough. But it's introducing an
inconsistency. Why should we copy rvalues and not lvalues
arguments? It's also inconsistent with std::bind.

#3 bind arguments as copies (including reference wrappers). When
"forwarding" the arguments to the inner function we can "unpack"
references from reference wrappers. This is what std::bind
does (I'm not sure about whether the unpacking is explicit or
implicit).

In any case, we *have* to store arguments in some way (either by value
or by reference) because there's no other way of passing them to a new
thread. So, it's like std::bind which returns a polymorphic function
the new thread can invoke without knowing anything about the
parameters.

The only difference between std::thread's constructor and std::bind is
that we can guarantee that the thread only invokes the function once
which allows us to handle move-only types as well.

I'm very much against storing references by default in std::bind or
std::thread for reasons mentioned above.

> > I think it's dangerous to "capture lvalues by reference". What
> > if you really want the thread to work on a copy? You'd have to
> > explicitly create a temporary rvalue and pass it to
> > std::thread's constructor.
>
> No, you'd have to explicitly create a separate lvalue and pass it
> to thread's constructor -- exactly what you'd have to do in the
> single-threaded case.

But then I'd be burdened with keeping the objects alive long enough.
This is very undesirable. If the constructor call would block as long
as the function is being executed we're fine. But that's not the idea
of threads, is it?

> > I personally prefer a std::thread constructor that behaves like
> > std::bind where every parameter is bound. If I really want to
> > pass a reference to an object to the thread I can use a
> > reference wrapper.
>
> Why do you prefer bind-compatible behavior here instead of
> function-call-compatible behavior?

Because that is what is happening with the thread. You create a
function object that is "later" executed (by another thread) and not
guaranteed to be finished when the constructor call returns. This is a
big difference to "function-call" behaviour.

> Note that nothing is being bound. There is
> no need for any special temporaries to be created. Parameters
> passed to the new thread can be stored in registers or put on
> the (thread-specific) stack, just
> as with the single-threaded case.

I would like to see a posic-based implementation of that. :-)

> Why should a developer expect that invoking a
> function on the current thread's stack should behave
> differently (for argument-passing purposes) than invoking the
> same function on the same arguments
> to be run on a different thread's stack?

Because it's a sane thing to do. It's a very different thing from
writing a function that is executed in just one thread while the
caller blocks until the function is done.

Cheers!
SG

SG

unread,
Jul 1, 2009, 1:30:50 PM7/1/09
to
SG wrote:
> It's "wrong" (or better undesirable) because the thread works
> asynchronously and may be stuck with dangling references.
> You basically have three choices:
>
> #1 bind lvalue and rvalue references (no copying whatsoever)
> It is doomed to fail. How would I make the thread work on its
> own copy? I can't. I'm stuck with creating a copy manually in
> the invoking thread and have to make sure it stays alive long
> enough before I can destroy it. Of course, this it not going
> to work with temporary rvalues:>
>
> std::thread t1 ( []{foo();} );

This was a bad example since the function object is copied anyways.
Here's another example where the argument (i) is actually an lvalue
but still short-lived and non-constant:

vector<thread> tvec;
for (int i=0; i<10; ++i) {
tvec.push_back(thread(do_something,i));
}
for (auto& t : tvec) {
t.join();
}

If we apply your rules, Scott, this function will work on a possibly
dangling reference that once pointed to i or the value has already
changed. Obviously we wanted to copy i but your rules force me to
write

tvec.push_back(thread(bind(do_something,i)));

instead.

I think this "bind" behaviour should be the default because it's less
error-prone with respect to the life-time management of objects. As a
bonus std::thread's constructor could support move-only types as well
(something that binders don't support). Using these rules, you can't
do a lot wrong. If the function expexts a reference-to-non-const
parameter and you don't pass a reference wrapper to thread's
constructor then it is simply ill-formed. In other cases you "just"
have additional copies and don't need to worry about their life-time.

> Scott Meyers wrote:
> > Note that nothing is being bound. There is
> > no need for any special temporaries to be created. Parameters
> > passed to the new thread can be stored in registers or put on
> > the (thread-specific) stack, just
> > as with the single-threaded case.
>

> I would like to see a posic-based implementation of that. :-)

Typo, I meant "Posix-based"


Cheers!
SG

Dragan Milenkovic

unread,
Jul 1, 2009, 1:31:15 PM7/1/09
to
Scott Meyers wrote:
> Scott Meyers wrote:
>> Consider:
>>
>> void increment(int& i) { ++i; }
>>
>> increment(10); // error -- can't bind rvalue to lref
>> int x = 0;
>> increment(x);
>> assert(x == 1); // succeeds
>>
>> No surprises. Now consider the exact same code launched in a separate
>> thread:
>>
>> std::thread t1(increment, 10); // compiles (see below)
>>
>> std::thread t2(increment, x);
>> t2.join();
>> assert(x == 1); // fails (see below)
>
> Apologies for following up to my own post, but I think I may have just
> had an
> "Aha!" moment. Maybe the way to say "make the argument-passing
> semantics of
> this cross-thread call the same as they'd be for a within-thread call"
> is to use
> a lambda (untested code):
>
> std::thread t1([]{ increment(10); }); // error -- 10 an rvalue
> std::thread t2([&]{ increment(x); }); // fine, increments x

I'm not familiar with the "deeper" workings of lambdas. What is the life
time of a closure object (and where)? Will it work with threads? Will it
work after "t1" gets destroyed while the thread is still running?

And why isn't thread ctor doing perfect forwarding? I got the impression
it should when reading the WP...

--
Dragan

darkmx

unread,
Jul 2, 2009, 1:07:46 AM7/2/09
to
> [ your news-reader. If that fails, use mailto:std-...@netlab.cs.rpi.edu]

> [ --- Please see the FAQ before posting. --- ]
> [ FAQ:http://www.comeaucomputing.com/csc/faq.html ]

Having implemented this myself (to workaround the lack of std::thread
for gcc 4.4.0 for windows) I think I can answer this.
Basically everything is resumed here:
http://www.justsoftwaresolutions.co.uk/threading/multithreading-in-c++0x-part-3.html

Currently the wording is problematic since the use of INVOKE(f, p...)
may confuse more than one. However we should consider LWG issue 929
http://home.roadrunner.com/~hinnant/issue_review/lwg-active.html#929
which is already under review and has the current wording,
specifically

"Constructs the following objects in memory which is accessible to a
new thread of execution as if:
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 the wi...
refers to the elements stored in the tuple w"

std::thread -does- perfect forwarding, but we will need to use
std::reference_wrapper to achieve reference semantics for parameters.
My implementation behaves like that.

Hope this helps Scott

Scott Meyers

unread,
Jul 2, 2009, 1:04:45 AM7/2/09
to
SG wrote:
> I don't think the "typical perfect forwarding" is possible. The
> arguments have to be stored somewhere somehow. You could try to store
> references only (to lvalues and rvalues) but that would be very bad in
> case you pass a temporary object to it or something that changes its
> state quickly and ceases to exist (like a counter of a for-loop) since
> the constructor possibly returns /before/ the function has finished
> executing so it may work with dangling references.

Callers have to deal with lifetime issues anyway, so this is a non-argument.
For example, if the invoked function takes a pointer instead of a reference
argument, the lifetime issues return. In fact, the issues return with any type
that has reference semantics (e.g., a shared_ptr).

The way to deal with lifetime problems is to educate programmers about them.
It's not to turn a pass-by-reference parameter declaration into something that
(1) actually passes by value and (2) accepts rvalue arguments when they'd be
illegal everywhere else in the language.

Scott

Fabio Fracassi

unread,
Jul 2, 2009, 1:02:59 AM7/2/09
to
On 1 Jul., 10:36, Scott Meyers <use...@aristeia.com> wrote:
> Scott Meyers wrote:
> > Consider:
>
> > void increment(int& i) { ++i; }
>
> > increment(10); // error -- can't bind rvalue to lref
> > int x = 0;
> > increment(x);
> > assert(x == 1); // succeeds
>
> > No surprises. Now consider the exact same code launched in a separate
> > thread:
>
> > std::thread t1(increment, 10); // compiles (see below)
>
> > std::thread t2(increment, x);
> > t2.join();
> > assert(x == 1); // fails (see below)
>
> Apologies for following up to my own post, but I think I may have just had an
> "Aha!" moment. Maybe the way to say "make the argument-passing semantics of
> this cross-thread call the same as they'd be for a within-thread call" is to use
> a lambda (untested code):
>
> std::thread t1([]{ increment(10); }); // error -- 10 an rvalue
> std::thread t2([&]{ increment(x); }); // fine, increments x

I think the Paper N2889 (http://www.open-std.org/jtc1/sc22/wg21/docs/
papers/2009/n2889.html,
Heading "Execution Policies") from the last mailing extends on why
this is a good idea for
different reasons.

regards

Fabio

Scott Meyers

unread,
Jul 2, 2009, 1:04:28 AM7/2/09
to
> Is it safe to say that if I want the argument-passing syntax and
> semantics of a
> within-thread call, but I want to have the invoked function run on another
> thread, I probably want to use a lambda as the function to pass to the
> thread
> constructor, the packaged_task constructor, etc.?

As yet another followup to my own post, I see that in N2901, Herb Sutter
proposes getting rid of the variadic thread constructor, echoing an idea from
N2889 that notes that this "would make the referencing environment of the
executed function quite explicit in the form of the lambda-capture." I very
much like this idea. The argument-passing issue I've raised wrt the thread
constructor would go away, and it would unify the approach of thread creation,
conditional variable waiting, and packaged_task creation. (Currently, the first
supports passing functions and arguments, but the latter two support passing
functions only.) The resulting API would be simpler, more uniform, and a lot
less confusing.

Regardless of whether Herb's proposal is accepted, I'm increasingly convinced
that the best way to pass functions to the thread API is to use lambdas. Are
there any downsides I'm overlooking?

SG

unread,
Jul 2, 2009, 1:06:53 AM7/2/09
to
Dragan Milenkovic wrote:
>
> And why isn't thread ctor doing perfect forwarding? I got the impression
> it should when reading the WP...

Consider this:

void foo(int j) {
// do something with j
}

int main() {
thread t (foo,42);
// At this point foo should have already been started because
// the temporary int (42) doesn't exist anymore.
t.join();
}

If you want "perfect-forwarding"-like behaviour here (in this case:
pass an rvalue reference to foo inside the new thread) you'd have to
make sure that j is created /before/ the thread's constructor call
finishes because after that the temporary doesn't exist anymore. The
"rvalue reference" that has to be stored somewhere (somehow) needs to
be valid and not dangling when j is initialized with it. How would you
ensure that? From what I can tell this would require compiler magic.
The constructor needs to block until the foo's parameters have been
created. To notify the parent thread that it's okay to unblock the
constructor call and destroy the temporary the new thread has to first
initialize foo's parameters, then notify the parent thread and then
start foo. This is not possible without compiler magic. Also, I think
it's undesirable to let the constructor block.

I'd still prefer a more binding-like behaviour (copy/move everything
and forward by rvalue reference or unpacked reference from reference
wrapper). This has at least three advantages:

(1) It's less error-prone. Multithreaded coding is already
complicated enough. If a functor takes a reference parameter
(both, const or non-const) it might lead to unintentional
data races. The "binding" approach forces you to use a
reference wrapper, thus, making it explicit that a reference
and not a copy is forwarded.

(2) std::thread's constructor doesn't need to block until the
stack parameters have been initialized in the new thread.

(3) It's actually implementable without compiler magic. The problem
in the other approach is the notification of the parent thread
/after/ stack parameter initialization and /before/ excecution
of the function -- something you can't do in plain C++.


Cheers!
SG

--

Dragan Milenkovic

unread,
Jul 2, 2009, 1:03:38 AM7/2/09
to
SG wrote:
> Dragan Milenkovic wrote:
>> I think that WP states that the thread constructor does perfect
>> forwarding of it's arguments to the function.
>
> I don't think the "typical perfect forwarding" is possible. The
> arguments have to be stored somewhere somehow. You could try to store
> references only (to lvalues and rvalues) but that would be very bad in
> case you pass a temporary object to it or something that changes its
> state quickly and ceases to exist (like a counter of a for-loop) since
> the constructor possibly returns /before/ the function has finished
> executing so it may work with dangling references. std::thread's
> constructor behaves more like a binder -- or at least it should which
> implies copying the arguments and forwarding the copies with a
> reference_wrapper-aware technique.

It's not a problem if the constructor returns _before_ the function
finishes executing. The problem is when the constructor returns
before the function has even been entered. So much for forwarding...

Is it alright to say that the inverse of perfect forwarding would
be a nice feature? :-D

Given:
void thread_proc(Foo by_value, Bar & by_reference)

If there could be a way for the std::thread constructor to reflect
the parameters that "thread_proc" expects and save the first one
by value and the second one by reference. Forwarding makes
arguments propagate unchanged down to the target function,
where this feature makes parameter types (function interface)
propagate up.

Anyway, as Scott noted, argument passing semantics raise a few
questions. My question is why does the constructor look like
it would forward the arguments (Args && ... args)? Maybe we should
remove this constructor and pass arguments only using lambdas?

I have no idea if all this would be a good thing or not. Just sharing
my thoughts...

--
Dragan

Scott Meyers

unread,
Jul 2, 2009, 1:04:10 AM7/2/09
to
SG wrote:
> This was a bad example since the function object is copied anyways.
> Here's another example where the argument (i) is actually an lvalue
> but still short-lived and non-constant:
>
> vector<thread> tvec;
> for (int i=0; i<10; ++i) {
> tvec.push_back(thread(do_something,i));
> }
> for (auto& t : tvec) {
> t.join();
> }
>
> If we apply your rules, Scott, this function will work on a possibly
> dangling reference that once pointed to i or the value has already
> changed. Obviously we wanted to copy i but your rules force me to
> write
>
> tvec.push_back(thread(bind(do_something,i)));
>
> instead.

Presumably you're assuming that doSomething takes its argument by reference. If
so, yes, this code has a problem, but the solution is to fix the calling code
(which is improperly ignoring lifetime issues), not silently ignore the declared
parameter-passing mechanism.

You can't save naive callers from themselves. Suppose doSomething was declared
to take a pointer:

void doSomething(int *p);

and the "bad" loop correspondingly modified:

tvec.push_back(thread(do_something,&i));

This code is just as broken as the code using references, and the "we're going
to ignore the declared parameter type and use pass-by-value instead" fix won't
help, because copying the pointer gives you the same pointer.

Anyway, as I've posted elsewhere, it looks like this whole issue may go away,
because it has been proposed that the variadic thread constructor be eliminated.
Callers can then use lambdas, which gives them explicit lifetime control over
passed arguments.

Scott

Scott Meyers

unread,
Jul 2, 2009, 1:05:26 AM7/2/09
to
Dragan Milenkovic wrote:

> Scott Meyers wrote:
>> std::thread t1([]{ increment(10); }); // error -- 10 an rvalue
>> std::thread t2([&]{ increment(x); }); // fine, increments x
>
> I'm not familiar with the "deeper" workings of lambdas. What is the life
> time of a closure object (and where)? Will it work with threads? Will it
> work after "t1" gets destroyed while the thread is still running?

A lambda expression yields an rvalue closure object. Presumably this is a
temporary, which means that it lives until the end of the call to the thread
constructor. The function object passed to the thread constructor is copied, so
the closure object certainly lasts long enough to be copied. The lifetime of
the values of the variables referenced by the closure depends on how the lambda
is defined (whether such values are referenced or copied), and that is under the
control of the person writing the lambda.

> And why isn't thread ctor doing perfect forwarding? I got the impression
> it should when reading the WP...

Yes, the WP is anything but transparent on this issue. Check out Issue 929 at
http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#929 , although I'm
now hoping that the committee eliminates the variadic constructor, as I've
posted elsewhere in this thread. That solution will effectively bless the
lambda solution and make the problem I originally posted about go away.

Scott

--

SG

unread,
Jul 2, 2009, 12:55:38 PM7/2/09
to
On 2 Jul., 07:07, darkmx <micael.d...@gmail.com> wrote:
> std::thread -does- perfect forwarding, but we will need to use
> std::reference_wrapper to achieve reference semantics for parameters.

If by "std::thread -does- perfect forwarding" you mean that it uses a
"perfect forwarding"-typical signature...

template<typename F, typename... Args>

thread(F&& f, Args&&.. args);

...then yes. But arguments are copied so the forwarding is not
"perfect."

Thanks for pointing out LWG issue 929.

Cheers!
SG

Dragan Milenkovic

unread,
Jul 2, 2009, 7:11:50 PM7/2/09
to

The worst part is that I fully understand how threads work and
get spawned. But somehow the thread that was writing posts was
out of sync with my logic thread. Anyway, I've sent another
post saying that forwarding was wrong. But thanks for taking the time
to clear things up...

It's nice that that other people are already working on removing
the variadic constructor in favor of lambdas.

--
Dragan

SG

unread,
Jul 3, 2009, 11:16:43 AM7/3/09
to
On 3 Jul., 01:11, Dragan Milenkovic <dra...@plusplus.rs> wrote:
>
> It's nice that that other people are already working on removing
> the variadic constructor in favor of lambdas.

Though, it would have been nice though to be able to pass move-only
type arguments to for use with the functor. I don't think a lambda
expression can capture move-only type objects by value. So, if this
thread constructor is removed we'd have to write our own "one-shot-
binder" to support move-only types. Example:

void foo(unique_ptr<matrix> pm);

unique_ptr<matrix> pm (new matrix(8,8));
for (int i=0; i<8; ++i)
for (int j=0; j<8; ++j)
(*pm)(i,j)=i+j;

std::thread t ([=]{foo(pm);}); // ill-formed

std::thread t (bind_once(foo,pm)); // OK

(Assuming bind_once behaves like std::bind with the exception that it
forwards the copied/moved argument as rvalue references instead of a
references-to-const)

Cheers!
SG

--

Dragan Milenkovic

unread,
Jul 3, 2009, 4:00:21 PM7/3/09
to
SG wrote:
> On 3 Jul., 01:11, Dragan Milenkovic <dra...@plusplus.rs> wrote:
>> It's nice that that other people are already working on removing
>> the variadic constructor in favor of lambdas.
>
> Though, it would have been nice though to be able to pass move-only
> type arguments to for use with the functor. I don't think a lambda
> expression can capture move-only type objects by value. So, if this
> thread constructor is removed we'd have to write our own "one-shot-
> binder" to support move-only types. Example:
>
> void foo(unique_ptr<matrix> pm);
>
> unique_ptr<matrix> pm (new matrix(8,8));
> for (int i=0; i<8; ++i)
> for (int j=0; j<8; ++j)
> (*pm)(i,j)=i+j;
>
> std::thread t ([=]{foo(pm);}); // ill-formed
>
> std::thread t (bind_once(foo,pm)); // OK
>
> (Assuming bind_once behaves like std::bind with the exception that it
> forwards the copied/moved argument as rvalue references instead of a
> references-to-const)

I see your point...

void func(const Foo & a, Foo && b, Foo & c);

std::thread t(
func,
copy_by_value,
std::move(move_by_value),
std::ref(by_reference)
);

... right?

Maybe lambda could be extended to support "move capture",
so that we get a round up feature?

Anyway, won't any potential error messages show only
at the point func gets invoked? Being a template, wouldn't
that be the point of instantiation of the call? Won't this
expose too much of the implementation detail? On the other
hand, lambdas will get checked before the are passed
to the thread constructor...

--
Dragan

Scott Meyers

unread,
Jul 4, 2009, 1:49:34 AM7/4/09
to
SG wrote:
> Though, it would have been nice though to be able to pass move-only
> type arguments to for use with the functor. I don't think a lambda
> expression can capture move-only type objects by value.

No, my understanding is that rref captures are not supported.

> So, if this
> thread constructor is removed we'd have to write our own "one-shot-
> binder" to support move-only types. Example:
>
> void foo(unique_ptr<matrix> pm);
>
> unique_ptr<matrix> pm (new matrix(8,8));
> for (int i=0; i<8; ++i)
> for (int j=0; j<8; ++j)
> (*pm)(i,j)=i+j;
>
> std::thread t ([=]{foo(pm);}); // ill-formed

Yes, but won't capture by reference, i.e.,

std::thread t( [&]{ foo(std::move(pm)); } );

do what you want?

Scott

SG

unread,
Jul 5, 2009, 2:26:38 AM7/5/09
to
On 4 Jul., 07:49, Scott Meyers <use...@aristeia.com> wrote:

> SG wrote:
>
> > So, if this
> > thread constructor is removed we'd have to write our own "one-shot-
> > binder" to support move-only types. Example:
> >
> > void foo(unique_ptr<matrix> pm);
> >
> > unique_ptr<matrix> pm (new matrix(8,8));
> > for (int i=0; i<8; ++i)
> > for (int j=0; j<8; ++j)
> > (*pm)(i,j)=i+j;
> >
> > std::thread t ([=]{foo(pm);}); // ill-formed
>
> Yes, but won't capture by reference, i.e.,
>
> std::thread t( [&]{ foo(std::move(pm)); } );
>
> do what you want?

No, it does not. Copying/Moving is deferred to the new thread which
requires explicit synchronization in many cases. Why making it more
complicated than it has to be? The fact that the constructor may
return before foo is invoked should strike you as something that is /
very/ different from a "normal" function call (potential argument life-
time and data race issues if the arguments are not copied/moved.)

// Example: deferred move-construction
// with synchronization

std::mutex mtx;
std::condition can_unblock;
bool b = false;
std::thread t ( [&]{
unique_ptr<matrix> p2 = std::move(pm);
{
std::lock_gard lck (mtk);
b = true;
can_unblock.notify_all();
}
foo(std::move(p2));
});
{
std::lock_gard lck (mtk);
can_unblock.wait(lck,[&]{return b;});
}
// Now I can safely destroy pm
// and move the thread object around
// or detach.

(The syntax w.r.t. mutexes, locks is probably not 100% correct but you
get the idea.)


Cheers!
SG

SG

unread,
Jul 5, 2009, 4:55:39 AM7/5/09
to
Dragan Milenkovic wrote:
>
> I see your point...
>
> void func(const Foo & a, Foo && b, Foo & c);
>
> std::thread t(
> func,
> copy_by_value,
> std::move(move_by_value),
> std::ref(by_reference)
> );
>
> ... right?

Yes, that's the idea. A function taking a reference-to-non-const would
require reference wrappers on the call site. Anything else would be
ill-formed. It's still the behaviour I like best. However, it is
probably not the behaviour that was intended (the draft is not very
specific in this case and it doesn't mention reference wrappers).

> Maybe lambda could be extended to support "move capture",
> so that we get a round up feature?

I like this idea. I think we would need a special syntax:

std::thread t1 ([pm]{foo(pm);}); // ill-formed #1
std::thread t2 ([-pm]{foo(pm);}); // OK #2

#1 is ill-formed because pm is not copy constructible. #2 would be
well-formed because pm is explicitly moved into the lambda object. So,
in addition to "pm" (capture by value), "&pm" (capture by reference)
we'd have "-pm" (capture by move construction).

> Anyway, won't any potential error messages show only
> at the point func gets invoked? Being a template, wouldn't
> that be the point of instantiation of the call? Won't this
> expose too much of the implementation detail?

That's what concepts are for. I think this constructor template is
supposed to be properly constrained so that an instantiation attempt
won't result in error messages that expose any std::thread
implementation details.

Cheers!
SG


--

Dragan Milenkovic

unread,
Jul 5, 2009, 4:55:31 AM7/5/09
to
Scott Meyers wrote:
>
> SG wrote:
> > Though, it would have been nice though to be able to pass move-only
> > type arguments to for use with the functor. I don't think a lambda
> > expression can capture move-only type objects by value.
>
> No, my understanding is that rref captures are not supported.
>
> > So, if this
> > thread constructor is removed we'd have to write our own "one-shot-
> > binder" to support move-only types. Example:
> >
> > void foo(unique_ptr<matrix> pm);
> >
> > unique_ptr<matrix> pm (new matrix(8,8));
> > for (int i=0; i<8; ++i)
> > for (int j=0; j<8; ++j)
> > (*pm)(i,j)=i+j;
> >
> > std::thread t ([=]{foo(pm);}); // ill-formed
>
> Yes, but won't capture by reference, i.e.,
>
> std::thread t( [&]{ foo(std::move(pm)); } );
>
> do what you want?

No, it won't... Move will occur only when the thread starts.
By this time 'pm' might already be destroyed. The point is
to save (move) 'pm' just like any other argument that is copied
by value (and then move it again when the thread starts).

Of course, what you have written is correct, but doesn't
cover the general case.

--
Dragan

Scott Meyers

unread,
Jul 6, 2009, 11:28:40 AM7/6/09
to
SG wrote:
> On 4 Jul., 07:49, Scott Meyers <use...@aristeia.com> wrote:
>> Yes, but won't capture by reference, i.e.,
>>
>> std::thread t( [&]{ foo(std::move(pm)); } );
>>
>> do what you want?
>
> No, it does not. Copying/Moving is deferred to the new thread which
> requires explicit synchronization in many cases. Why making it more
> complicated than it has to be? The fact that the constructor may
> return before foo is invoked should strike you as something that is /
> very/ different from a "normal" function call (potential argument life-
> time and data race issues if the arguments are not copied/moved.)

I think there are two issues here. Let's begin by considering the non-lambda
version of the call using the current draft's argument-copying rule:

std::thread t(foo, std::move(pm));

I believe you and I agree that the thread constructor may return before foo is
invoked. But may the thread constructor return before foo's parameter is
initialized? We know from the rules in C++98 that foo's parameter will be
initialized before foo is executed, so if we also know that foo's parameter will
be initialized before the thread constructor returns, we're in good shape.

The current draft's argument-copying semantics require that the result of
"std::move(pm)" be copied into a tuple before the thread constructor returns.
That being the case, I don't see any reason why the draft could not say that in
this statement,

std::thread t(f, x, y, z);

by the time the thread constructor returns, the arguments x, y, and z will have
been used to initialized the corresponding parameters in f. For by-reference
parameters (the only ones that are interesting for purposes of this discussion),
this could mean that pointers to those arguments have been copied somewhere,
possibly the the nascent thread's stack or to a register. Heck, the
implementation could copy the pointers into the tuple the draft seems so
interested in creating.

All the standard would have to do is replace the current guarantee that all
arguments are copied into a tuple prior to the thread constructor's return with
a guarantee that all arguments are used to initialize their corresponding thread
function parameters prior to the thread constructor's return. Note that this
says nothing about whether the thread function has started to execute. That
would continue to be unspecified.

Unfortunately, the above does not handle the lambda case. Consider the lambda
version of a generalized call, where I use "[...]" to avoid specifying a capture
mode:

std::thread t([...]{ f(x, y, z); });

In this case, the thread function being called is ClosureType::operator(), and
the only argument being passed to it is *this. If we follow my suggested rule
above, that means *this is initialized before the thread constructor returns,
and that's enough for us to know that the closure's constructor has run to
completion, i.e., that all the closure's data members have been fully
initialized before the thread constructor returns. But look again at the lambda
I proposed:

std::thread t( [&]{ foo(std::move(pm)); } );

Here, even with the semantics I am proposing, there is no guarantee that pm has
been moved to foo's parameter, because the call to foo is inside
ClosureType::operator(), i.e., inside the thread function, and we don't have
(and I'm not proposing) any guarantee that the body of the thread function has
started executing by the time the thread constructor returns.

The obvious way to solve this for move-only types is to enable rref captures in
lambdas:

std::thread t( [&&pm]{ foo(std::move(pm)); } );

Other threads have demonstrated, I think, that rref-captures would be useful for
lambdas, and I think this is more evidence to back that argument.

So this is what I conclude:
- Lambdas should support rref captures.
- Thread constructors should guarantee that by the time they return, all formal
parameters of the thread function have been initialized.

With these two things, I think we get cross-thread argument-passing semantics
that are analogous to synchronous argument-passing semantics, which I continue
to think is a huge improvement over the proposed "we'll copy your argument no
matter what the prototype of the function you are calling says" policy.

Scott

Dragan Milenkovic

unread,
Jul 6, 2009, 3:10:27 PM7/6/09
to
Scott Meyers wrote:
[snip]

The obvious way to solve this for move-only types is to enable
rref captures in
lambdas:

std::thread t( [&&pm]{ foo(std::move(pm)); } );

Other threads have demonstrated, I think, that rref-captures would
be useful for
lambdas, and I think this is more evidence to back that argument.

So this is what I conclude:
- Lambdas should support rref captures.


Is this the right term? To me it suggests we "grab" and save a rvalue
reference (in the same way lref capture saves a lvalue reference).
However, this feature should move the object into the closure, right?
Do you find this ugly: [=&& pm] ? resembles "operator=(Foo && pm)"?

--
Dragan

SG

unread,
Jul 6, 2009, 8:15:12 PM7/6/09
to
Scott Meyers wrote:

> SG wrote:


> > Scott Meyers wrote:
> >> std::thread t( [&]{ foo(std::move(pm)); } );
> >> do what you want?

> I think there are two issues here. Let's begin by considering the non-lambda


> version of the call using the current draft's argument-copying rule:
>
> std::thread t(foo, std::move(pm));
>
> I believe you and I agree that the thread constructor may return before foo is
> invoked. But may the thread constructor return before foo's parameter is
> initialized?

I don't know what exactly was intended. But I would say "Yes." --
simply because you can't write C++ standard code that allows you to
guarantee this behaviour -- unless you use
SomeNativeThreadLibrary::sleep(1000); at the end of the thread's
constructor. ;-)

> We know from the rules in C++98 that foo's parameter will be
> initialized before foo is executed, so if we also know that foo's parameter will
> be initialized before the thread constructor returns, we're in good shape.

It depends on what we're talking about exactly. In case you want the
parent thread to take the addresses of lvalue arguments, store them,
and use them in the new thread to initialize the formal parameters, I
still have to disagree with you. In my opinion, it's a very bad idea
-- for reasons I already mentioned and am going to repeat.

This isn't even a matter of consistency or intuition. A thread is a
fundamentally different thing. The thread's constructor even comes
syntactically closer to std::bind than a "normal" function call -- and
that is what is actually happening. You create some kind of
polymorphic function object inside std::thread which stores parameters
"somehow" (by value or by reference) and let the new thread process it
later. You can basically define any semantics you like. Ideally this
would be semantics which easily allows you to do what you want and
make it rather safe to use by default. In your case it is neither. I
don't get the safety and I don't get to pass lvalues by value so the
thread can work in its own copy.

The "single-thread function call" model is simply too limited to
express *all* desirable things that you could possibly want. I
consider passing things by value to be a very desirable thing to do.
The "single-thread function call" model is either inherently broken
(if you store addresses of rvalues, too) or inconsistent (if you store
the addresses of lvalues only). Where's your safety w.r.t. argument
life-time and data races if your function takes a reference-to-const
and you pass an lvalue argument to the thread's constructor?

> The current draft's argument-copying semantics require that the result of
> "std::move(pm)" be copied into a tuple before the thread constructor returns.
> That being the case, I don't see any reason why the draft could not say that in
> this statement,
>
> std::thread t(f, x, y, z);
>
> by the time the thread constructor returns, the arguments x, y, and z will have
> been used to initialized the corresponding parameters in f.

1. It doesn't matter if the arguments are copied anyways.

2. I suppose it would be something between very difficult and
impossible to implement std::thread in a conforming way.
If you manage to do it this library won't be portable across
compilers because it requires some magic to separate
initialization of formal parameters and function body
execution. Why the separation? Because you need to insert a
signal for the parent thread so it can unblock and finish
executing the constructor.

> For by-reference
> parameters (the only ones that are interesting for purposes of this discussion),
> this could mean that pointers to those arguments have been copied somewhere,
> possibly the the nascent thread's stack or to a register.

Stack? Register? What if the OS/native thread library doesn't support
this? Is this supported by POSIX threads? I don't think so.

> Heck, the
> implementation could copy the pointers into the tuple the draft seems so
> interested in creating.

Yes. Using a tuple is a very good idea, IMHO. Let me try to sketch the
interesting parts of a potential std::thread implementation:

class abstract_callable
{
public:
virtual ~abstract_callable() {}
virtual void operator()() = 0;
};

template<typename Functor, typename ParamTuple>
class concrete_callable : public abstract_callable
{
Functor f;
ParamTuple params;
public:
template<typename... Args>
concrete_callable(Functor const& f, Args && ... args)
: f(f),
params(make_tuple(forward<Args>(args))) {}

template<typename... Args>
concrete_callable(Functor && f, Args && ... args)
: f(move(f)),
params(make_tuple(forward<Args>(args))) {}

void operator()()
{
// somehow unpack the tuple and forward parameters
// parameters to f. My variadic template skills are
// limited. Is it even possible? Anyhow ...
// std::forward makes sure that f can't take a reference-
// to-non-const to a "non-ref_wrapped" parameter because
// copied parameters are treated like rvalues.
forward_tuple_helper(f,params);
}
};

class thread
{
abstract_callable* pac;
[.....]

public:
template<typename Func, typename... Args>
thread(Func && f, Args && ... args)
{
typedef
typename remove_cv<
typename remove_reference<Func>::type
>::type
functor_t;

typedef
decltype(make_tuple(forward<Args>(args)...))
tuple_t;

pac = new concrete_callable<functor_t,tuple_t>(
forward<Func>(f),
forward<Args>(args)...
);

// lauch a new thread that will invoke
// the callable object via (*pac)();
}

[.....]
};

This does exactly what I like it to do -- thanks to std::make_tuple
being reference_wrapper-aware. The constructor has a binding-like
behaviour with the exception that move-only type objects are supported
and properly moved. It is safe with respect to the arguments' life
times and doesn't suffer from potential and unintentional data races.
If the function takes a reference-to-non-const the user is forced to
use reference wrappers. Anything else will be ill-formed because other
parameters are forwarded as rvalues. Still, it is possible to pass
references by using "ref(foo)" and "cref(bar)".

==> It is either ill-formed or it is safe (except when the
user creates reference wrappers and ignores life-time
and/or synchronization issues).

> [.....]


> But look again at the lambda I proposed:
>
> std::thread t( [&]{ foo(std::move(pm)); } );
>
> Here, even with the semantics I am proposing,

(which I shamelessly snipped away)

> there is no guarantee that pm has
> been moved to foo's parameter, because the call to foo is inside
> ClosureType::operator(), i.e., inside the thread function, and we don't have
> (and I'm not proposing) any guarantee that the body of the thread function has
> started executing by the time the thread constructor returns.

Right. Capturing by reference is a dangerous thing to do if threads
are involved. :)

> The obvious way to solve this for move-only types is to enable rref captures in
> lambdas:
>
> std::thread t( [&&pm]{ foo(std::move(pm)); } );
>
> Other threads have demonstrated, I think, that rref-captures would be useful for
> lambdas, and I think this is more evidence to back that argument.

I'm a bit confused. What is this capturing mode supposed to do? Does
it move-construct a value or does it store an rvalue reference? The
latter would actually be pointless (no difference to [&pm]). If you
capture by value via move-construction I find the syntax rather
confusing. It suggests that a reference is captured but it is not. In
my example I used the unary minus [-pm] to make it clear that pm is
moved into the lambda object ("taken away" from the enclosing scope,
think "set difference" here).

> So this is what I conclude:
> - Lambdas should support rref captures.
> - Thread constructors should guarantee that by the time they
return, all formal parameters of the thread function have been
initialized.

It would be nice to be able to move objects into a lambda object. I
disagree with you on your second conclusion. It doesn't matter when
the formal parameters are initialized (if all the actual arguments are
copied and copies are managed by std::thread). You made it clear,
though, that you don't like creating copies by default and prefer to
get bitten by unintentional life-time and data race issues.

I don't think that such a guarantee (regardless of its
implementability) is useful for the majority of cases (assuming
arguments are copied inside the constructor). If you require pass-by-
reference (std::ref, std::cref), you have the option of using explicit
synchronization between the two threads -- which you should do anyways
if you share data between threads.

> With these two things, I think we get cross-thread argument-passing semantics
> that are analogous to synchronous argument-passing semantics, which I continue
> to think is a huge improvement over the proposed "we'll copy your argument no
> matter what the prototype of the function you are calling says" policy.

I disagree. In my opinion this is not desirable.

Cheers!
SG

Scott Meyers

unread,
Jul 7, 2009, 12:23:26 AM7/7/09
to
Dragan Milenkovic wrote:
> Is this the right term? To me it suggests we "grab" and save a rvalue
> reference (in the same way lref capture saves a lvalue reference).
> However, this feature should move the object into the closure, right?

I'm not sure. In the use case we are discussing, if we also adopt my idea that
the thread constructor cannot return until the thread function's parameters have
been initialized, there is no need to do a move into the closure; an rref in
the closure suffices.

> Do you find this ugly: [=&& pm] ? resembles "operator=(Foo && pm)"?

Hmmm, so this moves us towards having to specify (1) the types of the closure's
constructor parameters and (2) the types of the corresponding data members.

Are there use cases not involving thread construction where having a closure
store an rref to a moveable-but-not-copyable type don't suffice?

Scott

--

Scott Meyers

unread,
Jul 7, 2009, 10:41:00 PM7/7/09
to
SG wrote:
>
> I don't know what exactly was intended. But I would say "Yes." --
> simply because you can't write C++ standard code that allows you to
> guarantee this behaviour

But I'm guessing you can write a compiler that guarantees this
behavior, and that's good enough for me.

You might object that we're in the library part of the standard, so we
shouldn't specify anything that can't be written in standard C++0x,
but I'm guessing you'll have trouble implementing a mutex that way,
and that's in the library part of the standard, too. The semantics
I'm advocating are either implementable or they are not. If they are
not, that's an excellent argument for rejecting them. Otherwise they
should be considered on their merit, IMO.

> This isn't even a matter of consistency or intuition. A thread is a
> fundamentally different thing. The thread's constructor even comes
> syntactically closer to std::bind than a "normal" function call -- and
> that is what is actually happening. You create some kind of
> polymorphic function object inside std::thread which stores parameters
> "somehow" (by value or by reference) and let the new thread process it
> later.

Fine. My objection is to the decision to specify that if the function
being called declares that an argument is to be passed by reference,
the semantics are silently changed to pass-by-value.

> The "single-thread function call" model is simply too limited to
> express *all* desirable things that you could possibly want. I
> consider passing things by value to be a very desirable thing to do.

And it is, provided the function being called specifies that that's
what's supposed to happen. I consider it fraud if what the function
signature specifies is not what happens.

> The "single-thread function call" model is either inherently broken
> (if you store addresses of rvalues, too) or inconsistent (if you store
> the addresses of lvalues only). Where's your safety w.r.t. argument
> life-time and data races if your function takes a reference-to-const
> and you pass an lvalue argument to the thread's constructor?

I've addressed this in other postings. The *only* way to get
race-free code in this language is by design. If a caller passes a
reference (or a pointer, an option you seem to consistently ignore) to
another thread and either thread will be writing, the two threads must
use some kind of synchronization, e.g., a mutex. Nothing will change
that. Your solution destroys the semantics of pass-by-reference and
claims the resulting code is safe. How does your solution help if a
pointer is passed?

My solution says that the semantics of argument passing are always the
same. If a function prototype says pass-by-value, you get
pass-by-value. If it says pass-by-reference, you get
pass-by-reference. If it says pass-by-pointer, you get
pass-by-pointer. My solution also says that anytime you have multiple
threads accessing a chunk of memory, you have to write thread-safe
code yourself: the compiler will not try to save you from yourself by
silently changing a pass-by-reference specification into a
pass-by-value implementation.

> 1. It doesn't matter if the arguments are copied anyways.

It matters hugely if the parameter specification is pass-by-reference.

> Stack? Register? What if the OS/native thread library doesn't support
> this? Is this supported by POSIX threads? I don't think so.

If the semantics I'm proposing can be reasonably implemented, that's
good enough. Compiler-writers have to write lots of platform-specific
code. If they have to do that here in order to avoid introducing
fraud in the way parameters are passed, that's fine by me.

> ==> It is either ill-formed or it is safe (except when the
> user creates reference wrappers and ignores life-time
> and/or synchronization issues).

And if the user passes pointers? Tell me about the safety in that case.

> copied and copies are managed by std::thread). You made it clear,
> though, that you don't like creating copies by default and prefer to
> get bitten by unintentional life-time and data race issues.

That's an interesting way to characterize my view.

What I don't like is copies being made when the function I'm calling
has a specification that says the parameter-passing mechanism is
pass-by-reference.

> arguments are copied inside the constructor). If you require pass-by-
> reference (std::ref, std::cref), you have the option of using explicit
> synchronization between the two threads -- which you should do anyways
> if you share data between threads.

On that we agree. The only thing we don't agree on is the
reasonableness of making the caller slap what should be the
semantically redundant ref/cref on a parameter in order to pass it the
way the callee says it will be passed.

Scott

Dragan Milenkovic

unread,
Jul 7, 2009, 10:55:33 PM7/7/09
to
SG wrote:
>
> Scott Meyers wrote:
>
>> SG wrote:
>>>
>>> Scott Meyers wrote:
>>>>
>>>> std::thread t( [&]{ foo(std::move(pm)); } );
>>>> do what you want?
>
>> I think there are two issues here. Let's begin by considering the non-lambda
>> version of the call using the current draft's argument-copying rule:
>>
>> std::thread t(foo, std::move(pm));
>>
>> I believe you and I agree that the thread constructor may return before foo is
>> invoked. But may the thread constructor return before foo's parameter is
>> initialized?
>
> I don't know what exactly was intended. But I would say "Yes." --
> simply because you can't write C++ standard code that allows you to
> guarantee this behaviour -- unless you use
> SomeNativeThreadLibrary::sleep(1000); at the end of the thread's
> constructor. ;-)

Before reading Scott's post, I would have been 100% with you.
But it seems there actually is some space to explore his proposal.

Here is how I understood it... (ugly pseudo code at approx. 2AM)

thread::thread(f, a0, a1, a2) {
stack = malloc(STACK_SIZE);
__init_stack_for_thread(stack, f, a0, a1, a2);

pid = _create_thread(__thread_start, stack);
}

... and we're done with the constructor, no sleep or wait...

__init_stack_for_thread would:
- copy or move constructor's arguments to the newly
created stack (which corresponds to creating a tuple)
- push arguments for f into the stack, which can
involve copying or referencing elements from the "tuple"
(optimization would be to eliminate double copying
or moving)
- write some additional data that would allow the
following function to work...

__thread_start is the starting function of the new thread
that would simply invoke the function "f". It can have
all the data on the supplied stack (f, frame-pointer,
location of pushed arguments, ...)

This pseudo code is missing some _very_ important parts
such as error handling, stack cleanup and how would
(if possible at all) __init_stack_for_thread be implemented
without exposing too much internals, but it is just a quick
interpretation of Scott's words.

Posix threads do support specifying a custom stack.

[shameless snip]

In conclusion... I agree with all of SG's arguments. However,
I would not so easily disregard Scott's intention to define
cross-thread argument passing that is analogous to a simple
function call. His post got me thinking about the issue...

--
Dragan

Dragan Milenkovic

unread,
Jul 8, 2009, 12:49:41 AM7/8/09
to
Scott Meyers wrote:
> Dragan Milenkovic wrote:
>> Is this the right term? To me it suggests we "grab" and save a rvalue
>> reference (in the same way lref capture saves a lvalue reference).
>> However, this feature should move the object into the closure, right?
>
> I'm not sure. In the use case we are discussing, if we also adopt my
> idea that
> the thread constructor cannot return until the thread function's
> parameters have
> been initialized, there is no need to do a move into the closure; an
> rref in
> the closure suffices.

You have assumed something that might not happen (although it got me
thinking, as I said in the other post). For now, let's assume that
the ctor can return befor parameters have been initialized...

>> Do you find this ugly: [=&& pm] ? resembles "operator=(Foo && pm)"?
>
> Hmmm, so this moves us towards having to specify (1) the types of the
> closure's
> constructor parameters and (2) the types of the corresponding data members.

Why would the types have to be specified? They are the same as with
capture by value, except that move is used instead of copy. This is why
I proposed "=&&", "=" means that we save the value, "&&" means that
we do not copy but move... SG's alternative was "-".

> Are there use cases not involving thread construction where having a
> closure
> store an rref to a moveable-but-not-copyable type don't suffice?

Of course... if you save a closure for later invocation.

std::unique_ptr<Foo> foo;

bar.register_callback([=&& foo] { foo->func(); });

--
Dragan

SG

unread,
Jul 8, 2009, 12:47:54 AM7/8/09
to
Scott Meyers wrote:
> Dragan Milenkovic wrote:
>
> > Do you find this ugly: [=&& pm] ? resembles "operator=(Foo && pm)"?
>
> Hmmm, so this moves us towards having to specify (1) the types of the closure's
> constructor parameters and (2) the types of the corresponding data members.
>
> Are there use cases not involving thread construction where having a closure
> store an rref to a moveable-but-not-copyable type don't suffice?

Hmm... So you really want an rvalue reference stored. But a named
rvalue reference differs only in one aspect from a named lvalue
reference: rvalue references bind to rvalues, lvalue references bind
to lvalues (and const rvalues). That's it. Named references are always
treated as lvalue expressions regardless of their kind of reference.
So, this "rref capture" doesn't buy you anything. You'd still have to
explicitly use std::move inside the body. There would be no difference
to simply capturing it by (a "normal") reference.

Why are you against the "store make_tuple(forward<Args>(args)...) and
forward parameters later"-idea? I don't accept "counteruntuitive"
because that is at least arguable. It is
(1) implementable in a reasonably portable way. I can't say that
about your proposal and its guarantee.
(2) it provides greatest control over what is copied (or not) when
and where. I can't say that about your proposal.
(3) it is hard to misuse it / screw up (data races, argument
life-time). I can't say that about your proposal.

Cheers!
SG

darkmx

unread,
Jul 8, 2009, 12:49:18 AM7/8/09
to
On 6 jul, 19:15, SG <s.gesem...@gmail.com> wrote:
> Scott Meyers wrote:
> > SG wrote:
> void operator()()
> {
> // somehow unpack the tuple and forward parameters
> // parameters to f. My variadic template skills are
> // limited. Is it even possible? Anyhow ...

Its possible but you need a little trick. Consider a tuple of size N,
you need a

template<int... I> struct index;

that when unpacked produces 0, 1, 2, 3, ..., N - 1; then you do
something like


apply(typename build_index<N>::type( ));

template<int... I>
void apply(index<I...>)
{
function(std::get<I>(tuple_here)...);
}


Scott, I agree that it would be better if the constructor waited for
the thread to be properly initialized, but this is not possible (at
least on windows) because initializing a thread just adds it to a
queue waiting for execution (it may or may not start running
immediatly). Using a lock/sleep to achieve the same effect would cause
approximately a full millisecond of overhead (I tested it, I was
trying to avoid using the free store in my implementation).

The closer of a solution I can think of is this_thread::yield
immedialty after calling the native thread launcher and before exiting
std::thread constructor in hope of the new thread to be already ready
when the next time slice is assigned to the thread creator, but you
aren't guaranteed anything and may be also pretty slow.

Anthony Williams

unread,
Jul 8, 2009, 6:32:04 AM7/8/09
to
Scott Meyers <use...@aristeia.com> writes:

> SG wrote:
>>
>> I don't know what exactly was intended. But I would say "Yes." --
>> simply because you can't write C++ standard code that allows you to
>> guarantee this behaviour
>
> But I'm guessing you can write a compiler that guarantees this
> behavior, and that's good enough for me.
>
> You might object that we're in the library part of the standard, so we
> shouldn't specify anything that can't be written in standard C++0x,
> but I'm guessing you'll have trouble implementing a mutex that way,
> and that's in the library part of the standard, too. The semantics
> I'm advocating are either implementable or they are not. If they are
> not, that's an excellent argument for rejecting them. Otherwise they
> should be considered on their merit, IMO.

I think the technical difficulties in such an implementation outweigh
the benefits. For one thing it *does* make it impossible to write this
part of the library in plain C++. For another, it means special-casing a
particular scenario in the compiler. It means that the one invocation of
foo that happens to be called as the thread function for a new thread
has to have different argument-passing semantics to all other calls to
foo, so that the compiler can notify the std::thread constructor that
the arguments are correctly constructed.

> Fine. My objection is to the decision to specify that if the function
> being called declares that an argument is to be passed by reference,
> the semantics are silently changed to pass-by-value.

std::thread can be constructed with callable objects, which can have
overloaded function call operators.

struct foo
{
template<typename T>
void operator()(T) const;

template<typename T,typename U>
void operator()(T&,U&) const;

int operator()(int,std::string const&) const;
};

Yes, the compiler can perform overload resolution to select between
them, when given a concrete set of parameters, but we have no way of
identifying what the signature of that overload is, and how to deduce
the way to pass the parameters. The closest we have is decltype and
result_of, but that only gives us the type of the result of calling with
a specific set of parameters --- it doesn't give us the signature of the
chosen overload.

This therefore is another special feature that must be added to a
compiler (if not the language) to deal with std::thread.

Anthony
--
Author of C++ Concurrency in Action | http://www.manning.com/williams
just::thread C++0x thread library | http://www.stdthread.co.uk
Just Software Solutions Ltd | http://www.justsoftwaresolutions.co.uk
15 Carrallack Mews, St Just, Cornwall, TR19 7UL, UK. Company No. 5478976

SG

unread,
Jul 9, 2009, 10:52:15 AM7/9/09
to
Hello Scott,

Scott Meyers wrote:
> Fine. My objection is to the decision to specify that if the
> function being called declares that an argument is to be passed
> by reference, the semantics are silently changed to pass-by-value.

The function still takes a reference or an object by value. The only
question is what object such a reference will refer to. In case of
forwarding in a single thread we can use perfect forwarding. No harm
done. In case of threads -- where execution of the invoking thread
continues while the new thread is still working -- we can't do that.
At least not for rvalues which might be temporaries that die
immediately when the "construction expression" has been fully
evaluated. In any case, the user should be able to control what is
copied when and where -- because it matters in the multi-threaded
case.

I'm not going to argue about "expected behaviour", "destroyed
semantics" because this is a matter of perspective, taste and very
subjective (translation: I don't agree with your take on this). Let's
just compare side by side what kind of functions there are, what kind
of forwarding would be desirable in those cases, how to do that
forwarding and what potential surprizes and user errors there are.
For convenience I will refer to the proposed semantics with the
letters "R" and "T":

"R-semantics": always store a [R]eference to lvalue arguments,
copy/move rvalues and let the constructor block (somehow, with
the help of the compiler) until the initialization of the
function's formal parameters has finished. The formal
parameters are initialized with lvalue references (original
lvalue arguments) and unnamed rvalue references (referring to
the copies).

"T-semantics": constructor doesn't block, arguments are stored in
a [T]uple via make_tuple(forward<Args1>(args)...). Forwarding
is done via func( forward<Args2>(get<Indices>(tup))... ) where
the type of tup is a tuple<Args2...>. Note: Args2 is not the
same parameter pack as Args1 due to type transformations in
make_tuple.

Case 1: The function takes its parameter by value

There's not much to discuss here. The only thing that comes to
mind is: if the argument to std::thread is an lvalue you can
save one copy operation with your "R-semantics". This can even
be emulated by using a reference wrapper cref() and explicit
synchronization with "T-semantics". Anyhow, both choices are
equally safe and free of bad surprizes.

Case 2: The function takes its parameter by reference-to-const and
DOES NOT expect the object to magically change its state like an
old-style "single-thread application" function. The reference-
to-const is only an optimization that is supposed to save a copy
operation. This might be a function that is also used in
"single-thread" contexts and/or a function that someone else
wrote (i.e. library).

With "R-semantics" you have to be very careful in this case not
to pass an lvalue that could die too soon or just change quickly.
If the invoking code changes or destroys the object too soon you
have a data race and/or a dangling reference. To solve this and
let the function work on its own copy you have to explicitly
create a temporary copy and pass this rvalue to the std::thread's
constructor.
==> potential surprize & user error.

With "T-semantics" this is not an issue. Lvalue arguments are
copied, too.

Case 3: The function takes its parameter by reference-to-const and
DOES expect the object's state to "magically" change. This is a
function that has been written with multi-threaded execution in
mind.

With "R-semantics" you get the desired behaviour without doing
anything special.

With "T-semantics" you have to use a reference wrapper (cref)
to achieve the goal. In this case the user could forget to do
that which has the effect that the function can't observe any
changes because the reference refers to an old copy.
==> potential surprize & user error.

Case 4: The function takes its parameter by reference-to-non-const
so it can change the object's state.

With "R-semantics" this is not a problem. Simply pass the lvalue
directly to std::thread's constructor.

With "T-semantics" this is not a problem. Simply use a reference
wrapper to make it compile. Anything else won't compile.

The question is, which of the two surprizes I mentioned above is more
likely to appear and/or more severe and/or harder to debug than the
other one.

> My solution says that the semantics of argument passing are always
> the same. If a function prototype says pass-by-value, you get
> pass-by-value. If it says pass-by-reference, you get
> pass-by-reference.

Sometimes (rather often, actually), I don't care about how a function
takes its parameters as long as it behaves like it had a pass-by-value
style behavior. It could take a reference-to-const or the object by
value. I simply don't care. It doesn't make a big difference in the
single-threaded case. But it DOES make a difference with your
proposed "R-semantics" because of potential data races and dangling
references.

You are suggesting that "T-semantics" introduces an incosistency that
is undesirable. I'm saying that "R-semantics" is not a successful
approximation of a function call in a single-threaded program and that
std::thread doesn't even have to try to approximate it. The
std::thread constructor call is a different concept. The design goals
ought to be convenience, flexibility, and safety. This includes
favoring unintentional copies over possibly dangling references or
possible data races in my book.

> [...]


> > 1. It doesn't matter if the arguments are copied anyways.
> It matters hugely if the parameter specification is
> pass-by-reference.

No, actually it does not. In case your function takes a reference
(either const or non-const) this guarantee is of no additional
benefit. You have to explicitly synchronize the access to the shared
data anyways. There is little difference between "R" and "T" in this
respect:

void func(int & lvref);

int lvalue = 0;
std::thread t (func, lvalue); // "R" semantics

int lvalue = 0;
std::thread t (func, ref(lvalue)); // "T" semantics

The constructor of the 2nd version just might return more quickly
because it doesn't have to block until func's reference has been
initialized. But that's about it.

> What I don't like is copies being made when the function I'm calling
> has a specification that says the parameter-passing mechanism is
> pass-by-reference.

I get that. What I don't like is that with "R-semantics" I might have
to explicitly create temporary copies of lvalues to avoid data races
and parameter life-time issues. In cases I don't know exactly whether
the function takes its parameter by value or by ref-to-const I need to
do that as a precaution. As mentioned, in a single-threaded version I
don't need to care about that.

On the bright side, a function that takes a reference-to-non-const
requires users to use reference wrappers to make the program compile
with "T-semantics". What I like about it is that it is explicit about
what actually happens. You immediately see which objects are copied
_within_ the parent thread and which are not by looking at the
"constructor call". What happens in the new thread is still
determined by the function's signature. Whether the function takes a
parameter by value or by reference-to-const won't make a big
difference unless you deal with "case 3" (see above).

> [...]


> The only thing we don't agree on is the
> reasonableness of making the caller slap what should be the
> semantically redundant ref/cref on a parameter in order to
> pass it the way the callee says it will be passed.

We also disagree on the "slap" and "semantically redundant" part.
:-)

This is what I meant by "user should be able to control what is copied
when and where -- because it makes a difference". The words "when and
where" also include which thread does the copying.

My conclusions so far:
Both approaches are prone to user errors. However, I consider the
first surprize (see case 2 above) to be more likely and harder to
debug than the 2nd surprize (see case 3 above). Also, "T-semantics"
is easier to implement (doesn't require compiler magic) and makes
forwarding references explicit at the call site which I consider to be
an advantage in terms of readability. The only rule one has to teach
is: "Anything you forward with a ref-wrapper needs explicit
synchronization. Anything else is copied and safe without explicit
synchronization". Simple rule. I like it.

Cheers!
SG

Scott Meyers

unread,
Jul 9, 2009, 1:54:58 PM7/9/09
to
Dragan Milenkovic wrote:
> Why would the types have to be specified? They are the same as with
> capture by value, except that move is used instead of copy. This is why
> I proposed "=&&", "=" means that we save the value, "&&" means that
> we do not copy but move... SG's alternative was "-".

Okay, I understand now. FWIW, I like =&& best, because it builds on existing
syntax.

Scott

--

Scott Meyers

unread,
Jul 9, 2009, 1:56:58 PM7/9/09
to
darkmx wrote:
> Scott, I agree that it would be better if the constructor waited for
> the thread to be properly initialized

This is not my proposal. My proposal is for the arguments to the thread
function to be initialized before the thread constructor returns. This is in
fact what is already specified via the current draft's "copy them into a
temporary tuple" provision. Per another post I just made, by-reference
parameters can be handled completely naturally using the existing technique by
simply copying the address of what is referenced into the tuple instead of
making a copy of it.

Scott

Scott Meyers

unread,
Jul 9, 2009, 1:57:41 PM7/9/09
to
Anthony Williams wrote:
> I think the technical difficulties in such an implementation outweigh
> the benefits. For one thing it *does* make it impossible to write this
> part of the library in plain C++. For another, it means special-casing a
> particular scenario in the compiler. It means that the one invocation of
> foo that happens to be called as the thread function for a new thread
> has to have different argument-passing semantics to all other calls to
> foo, so that the compiler can notify the std::thread constructor that
> the arguments are correctly constructed.

I think not. Consider the binary code generated from a function taking a
by-reference parameter. Almost certainly that parameter is actually passed by
pointer. That is, you write this in the source code,

void f(T& param);

and the underlying binary looks like this:

void f(T* param);

Assuming this is true, all the back end of the compiler has to do is treat
by-reference parameters like by-pointer parameters *in all cases*, which I'm
fairly certain is what it already does. The current semantics of thread
construction simply call for passing the address of a temporary, while I'm
advocating passing the address of whatever argument is passed in, i.e., the
exact same semantics you get with a synchronous function call.

As for "special-casing a particular scenario," have you not noticed
that this is
what I'm trying to get rid of? The current spec says "a pass-by-reference
parameter means blah blah blah, except when it's done through a thread
constructor, in which case the following contradictory special rule kicks in."

> std::thread can be constructed with callable objects, which can have
> overloaded function call operators.
>
> struct foo
> {
> template<typename T>
> void operator()(T) const;
>
> template<typename T,typename U>
> void operator()(T&,U&) const;
>
> int operator()(int,std::string const&) const;
> };

Valid point. With luck, the proposal in N2901 to eliminate the variadic thread
constructor will be adopted, and then this becomes a non-issue. Given
that most
functions taking function objects as parameters are not variadic (e.g.,
condition_variable::wait, the entire STL), having thread construction take only
a function object (but not any arguments for it) would bring it into uniformity
with the rest of the standard library.

Not that it matters, but given what I consider to be fundamentally broken
semantics of pass-by-reference via variadic thread construction in the draft
standard, my plan is to encourage programmers to use only lambdas, anyway.

Scott

--

Scott Meyers

unread,
Jul 9, 2009, 4:24:14 PM7/9/09
to
SG wrote:
>
> The function still takes a reference or an object by value. The only
> question is what object such a reference will refer to. In case of
> forwarding in a single thread we can use perfect forwarding. No harm
> done. In case of threads -- where execution of the invoking thread
> continues while the new thread is still working -- we can't do that.

Of course we can. I've posted elsewhere that all a compiler back end
has to do is pass by pointer, which is almost certainly how things are
currently implemented for references, anyway.

> At least not for rvalues which might be temporaries that die
> immediately when the "construction expression" has been fully
> evaluated.

Rvalues present absolutely zero implementation difficulties. Yes,
their lifetime can be problematic, but that's something synchronous
programmers have had to take into account for years:

std::string foo();
const char *p = foo().c_str();
std::cout << p; // undefined! the rvalue on which
// foo was invoked no longer exists

> In any case, the user should be able to control what is
> copied when and where -- because it matters in the multi-threaded
> case.

Users already have perfect control over that, and it matters in the
single-threaded case, too.

> "R-semantics": always store a [R]eference to lvalue arguments,
> copy/move rvalues and let the constructor block (somehow, with
> the help of the compiler) until the initialization of the
> function's formal parameters has finished. The formal
> parameters are initialized with lvalue references (original
> lvalue arguments) and unnamed rvalue references (referring to
> the copies).

The notion of blocking is misleading. There is no reason to think
this is mechanically any different from the copy-into-a-tuple
implementation you associate with T-semantics, and in fact the process
will be faster for non-ref-wrapped reference parameters, because there
will be no need to run a constructor for the copy.

> Case 1: The function takes its parameter by value

> Case 2: The function takes its parameter by reference-to-const

> Case 3: The function takes its parameter by reference-to-const

> Case 4: The function takes its parameter by reference-to-non-const

I have *repeatedly* asked about pass-by-pointer, and no one has
addressed it. So:

Case 5: The function takes its parameter by pointer to const
Case 6: The function takes its parameter by pointer to non-const

These cases behave the same under both R and T semantics with both r-
and lvalues. Both expose callers to lifetime issues and race
conditions. Under T semantics, however, cases 2-4 behave
fundamentally differently from cases 5-6, which introduces a new
inconsistency into the semantics of the language. Under R semantics,
no such inconsistency is introduced.

> data anyways. There is little difference between "R" and "T" in this
> respect:
>
> void func(int & lvref);
>
> int lvalue = 0;
> std::thread t (func, lvalue); // "R" semantics
>
> int lvalue = 0;
> std::thread t (func, ref(lvalue)); // "T" semantics
>
> The constructor of the 2nd version just might return more quickly
> because it doesn't have to block until func's reference has been
> initialized. But that's about it.

You overlook the otherwise-unnecessary need for the caller to remember
to slap a reference wrapper around lvalue. As for which constructor
returns more quickly, as I noted above, there is no reason to believe
that there is any difference in blocking behavior.

> I get that. What I don't like is that with "R-semantics" I might have
> to explicitly create temporary copies of lvalues to avoid data races
> and parameter life-time issues. In cases I don't know exactly whether
> the function takes its parameter by value or by ref-to-const I need to
> do that as a precaution.

If you don't know the prototype of a function you are calling, well,
that's just scary.

> an advantage in terms of readability. The only rule one has to teach
> is: "Anything you forward with a ref-wrapper needs explicit
> synchronization. Anything else is copied and safe without explicit
> synchronization". Simple rule. I like it.

You shouldn't. It's wrong. Once again I ask: what about
pass-by-pointer? Sure, it's copied, but the result is not safe without
synchronization.

Even if the rule were correct, it's still incomplete. You also have
to unlearn the rule that says pass-by-reference creates an alias. You
have to learn that pass-by-pointer is suddenly treated differently
from pass-by-reference.

My design offers consistency: parameter passing always has the same
semantics, everything you know about parameter-passing from
single-threaded programming continues to apply, and you are always and
completely responsible for addressing lifetime issues and data races.
Your design offers a hodge-podge of special-cases rules that sometimes
prevent races and sometimes do not (cases 5-6), that sometimes break
traditional semantics (case 3) and sometimes do not.

C++ is already a big, complicated, messy language. Your semantics
make it bigger, more complicated, and messier.

Scott

Bart van Ingen Schenau

unread,
Jul 9, 2009, 7:15:30 PM7/9/09
to
SG wrote:

I would regard this as a huge risk, because the problem will problem
will probably only be understood properly by a few excellent C++
programmers, but not the masses that will be using threads.
See below for an example how easily it can bite you.

>
> With "T-semantics" this is not an issue. Lvalue arguments are
> copied, too.
>
> Case 3: The function takes its parameter by reference-to-const and
> DOES expect the object's state to "magically" change. This is a
> function that has been written with multi-threaded execution in
> mind.
>
> With "R-semantics" you get the desired behaviour without doing
> anything special.
>
> With "T-semantics" you have to use a reference wrapper (cref)
> to achieve the goal. In this case the user could forget to do
> that which has the effect that the function can't observe any
> changes because the reference refers to an old copy.
> ==> potential surprize & user error.

As the function was especially written to be used as a thread-function,
the chances for surprise and error can be greatly reduced by documenting
the fact that the parameter should be wrapped in a cref.

>
> Case 4: The function takes its parameter by reference-to-non-const
> so it can change the object's state.
>
> With "R-semantics" this is not a problem. Simply pass the lvalue
> directly to std::thread's constructor.
>
> With "T-semantics" this is not a problem. Simply use a reference
> wrapper to make it compile. Anything else won't compile.
>
> The question is, which of the two surprizes I mentioned above is more
> likely to appear and/or more severe and/or harder to debug than the
> other one.
>

<snip>


> My conclusions so far:
> Both approaches are prone to user errors. However, I consider the
> first surprize (see case 2 above) to be more likely and harder to
> debug than the 2nd surprize (see case 3 above). Also, "T-semantics"
> is easier to implement (doesn't require compiler magic) and makes
> forwarding references explicit at the call site which I consider to be
> an advantage in terms of readability. The only rule one has to teach
> is: "Anything you forward with a ref-wrapper needs explicit
> synchronization. Anything else is copied and safe without explicit
> synchronization". Simple rule. I like it.

Here is an example that should show the consequences of the choices for
R- and T-semantics:

We start with a matrix class that exposes a row and column view over its
internal data as a (mathematical) vector, and a single-threaded function
to multiply two matrices:

class matrix {
public:
void resize(int rows, int cols);
double& at(int row, int col);
int num_rows() const;
int num_cols() const;
vector get_row(int num) const;
vector get_col(int num) const;
};

void math::dot_product(const math::vector& a,
const math::vector& b,
double& result);

void matrix_product(const math::matrix& a,
const math::matrix& b,
math::matrix& result)
{
if (a.num_cols() == b.num_rows())
{
result.resize(a.num_rows(), b.num_cols());

for (int i=0; i<a.num_rows(); i++)
{
for (int j=0; j<b.num_cols(); j++)
{
dot_product(a.get_row(i), b.get_col(j), result.at(i, j));
}
}
}
}


Now, with the introduction of multi-threading, some maintenance
programmer wants to speed up the matrix product, by calculating all the
dot_product calculation in parallel and comes up with this:


void matrix_product(const math::matrix& a,
const math::matrix& b,
math::matrix& result)
{
if (a.num_cols() == b.num_rows())
{
vector<thread> threads;

result.resize(a.num_rows(), b.num_cols());

for (int i=0; i<a.num_rows(); i++)
{
for (int j=0; j<b.num_cols(); j++)
{
threads.push_back(thread(dot_product, a.get_row(i),
b.get_col(j), result.at(i,j)));
}
}

for (auto& t : threads) {
t.join();
}
}
}

At first glance, there are no problems with this code and with R-
semantics the compiler will accept it as well. So, is the code correct?
No, it is not, because the dot_product function receives dangling
references to the vectors (remember that matrix::get_row and
matrix::get_col return a temporary object).
The fix for this issue requires that we store all those vectors to
ensure they live long enough. This could add quite an overhead to an
otherwise simple algorithm.

With T-semantics, the compiler will complain that 'result.at(i,j)' is
not wrapped. This alerts us to the fact that we have to ensure there is
no data race on that parameter. As there is not, we can easily placate
the compiler with this change:
threads.push_back(thread(dot_product, a.get_row(i), b.get_col(j),
ref(result.at(i,j))));
Now the function is perfectly safe.

>
> Cheers!
> SG
>

Bart v Ingen Schenau
--
a.c.l.l.c-c++ FAQ: http://www.comeaucomputing.com/learn/faq
c.l.c FAQ: http://c-faq.com/
c.l.c++ FAQ: http://www.parashift.com/c++-faq-lite/

SG

unread,
Jul 10, 2009, 4:13:07 AM7/10/09
to
Scott Meyers wrote:
> With luck, the proposal in N2901 to eliminate the variadic thread
> constructor will be adopted, and then this becomes a non-issue.
> [...]

> Not that it matters, but given what I consider to be fundamentally
> broken semantics of pass-by-reference via variadic thread
> construction in the draft standard, my plan is to encourage
> programmers to use only lambdas, anyway.

Sorry, but you can't say it's "fundamentally broken" just because you
don't like the fact that the std::thread constructor wraps parameters
in a tuple. As for it being "counterintuitive": I'm sure expectations
are highly subjective and differ in this case.

For now, it seems this variadic constructor is the only way of moving
objects into the new thread without writing your own functor class or
having to use explicit synchronization. std::bind doesn't support
move-only types at all. Lambdas don't (yet) support "move-capturing".

Let me just remind you of the similarities between the use of lambdas
(with hypothetical move-capturing "-") and the "tuple-capturing"
constructor you deem "fundamentally broken":

void foo(int i, unique_ptr<int> p, string & io);

int i=0;
unique_ptr<int> p (new int);
string io = "hello";

thread t ( [=,-p,&io] ()mutable {foo(i,move(p),io);} ); // #1
thread t ( foo, i,move(p),ref(io) ); // #2

Note the redundancy and verbosity of #1 and how every type of
forwarding translates between #1 and #2. Note also that #1 and #2 are
NOT simple function calls. They DON'T TRY to be. It's a different
concept which also deals with the details of argument forwarding (when
and where arguments are copied/moved or not).

Without move-capturing lambdas and without such a variadic
constructor, I would have to write my own generic wrapper which does
exactly what this constructor would have done: storing parameters via
make_tuple and forwarding it via forward<Args>(get<Indices>(tup))...

> > [...] In case of forwarding in a single thread we can use perfect


> > forwarding. No harm done. In case of threads -- where execution
> > of the invoking thread continues while the new thread is still
> > working -- we can't do that.
>
> Of course we can. I've posted elsewhere that all a compiler back end
> has to do is pass by pointer, which is almost certainly how things are
> currently implemented for references, anyway.

My point was about the arguments' life-times. I don't care much about
how some C++ ABI is implemented. At least I should not have to.

> > At least not for rvalues which might be temporaries that die
> > immediately when the "construction expression" has been fully
> > evaluated.
>
> Rvalues present absolutely zero implementation difficulties. Yes,
> their lifetime can be problematic, but that's something synchronous
> programmers have had to take into account for years:

...and you think it's a good idea to make it worse?

Please explain to me what actually happens with lvalue and rvalue
arguments. Maybe I misunderstood something ... I see argument life-
time and data-race issues all over the place w.r.t. my current
understanding of your proposal.

> > "R-semantics": always store a [R]eference to lvalue arguments,
> > copy/move rvalues and let the constructor block (somehow, with
> > the help of the compiler) until the initialization of the
> > function's formal parameters has finished. The formal
> > parameters are initialized with lvalue references (original
> > lvalue arguments) and unnamed rvalue references (referring to
> > the copies).
>
> The notion of blocking is misleading. There is no reason to think
> this is mechanically any different from the copy-into-a-tuple
> implementation you associate with T-semantics, and in fact the
> process will be faster for non-ref-wrapped reference parameters,
> because there will be no need to run a constructor for the copy.

"no need to run a constructor for the copy" in which cases? Lvalue
arguments? Rvalue arguments? Both? Tell me about the implications of
your proposal for this scenario:

struct user_type { int i; double d; };

// possible low-level implementation:
// void foo(user_type*, string const* text);
// (the caller is responsible for managing the
// objects and passes the addresses to foo)
//
void foo(ud_type k, string const& text) {
cout << text << k.i; // <--- garbage?
// data race for text, for k, or both?
}

thread launch(int i, string const& x)
{
user_type u = {i, 3.14};
return thread(foo,u,x);
}

int main() {
vector<thread> tv;
for (int i=0; i<10; ++i)
tv.push_back(launch(i,"hello"));
for (auto& t : tv)
t.join();
}

Is it broken? If yes, how can I fix it? Does your proposal introduce
an inconsistency between pass-by-value for built-in types and pass-by-
value for user-defined types? I'm asking because some ABIs might
always use pointers for user defined types. Will the reference "text"
possibly dangle? How do I fix this program without changing the
function foo?

> > Case 1: The function takes its parameter by value
> > Case 2: The function takes its parameter by reference-to-const
> > Case 3: The function takes its parameter by reference-to-const
> > Case 4: The function takes its parameter by reference-to-non-const
>
> I have *repeatedly* asked about pass-by-pointer, and no one has
> addressed it.

Deliberately. What does it have to do with anything?

What about "case 2"? Don't you agree that it is a potential pitfall
for your proposal?

> So:
>
> Case 5: The function takes its parameter by pointer to const
> Case 6: The function takes its parameter by pointer to non-const
>
> These cases behave the same under both R and T semantics with both r-
> and lvalues. Both expose callers to lifetime issues and race
> conditions. Under T semantics, however, cases 2-4 behave
> fundamentally differently from cases 5-6, which introduces a new
> inconsistency into the semantics of the language. Under R semantics,
> no such inconsistency is introduced.

Frankly, I don't know how to respond to that. What you wrote doesn't
make much sense to me. I think it's a matter of perspective. I don't
share your point of view and nothing you said so far has changed
that / convinced me of your proposal's superiority and/or "purity" --
in any respect. If anything, we made different trade-offs.

> > void func(int & lvref);
>
> > int lvalue = 0;
> > std::thread t (func, lvalue); // "R" semantics
>
> > int lvalue = 0;
> > std::thread t (func, ref(lvalue)); // "T" semantics
>
> > The constructor of the 2nd version just might return more quickly
> > because it doesn't have to block until func's reference has been
> > initialized. But that's about it.
>
> You overlook the otherwise-unnecessary need for the caller to remember
> to slap a reference wrapper around lvalue.

I didn't. I specifically addressed it in my text "case 4". I don't
consider this an issue. If you forget ref() in this case the code
won't compile. Nice, isn't it?

> > I get that. What I don't like is that with "R-semantics" I
> > might have to explicitly create temporary copies of lvalues
> > to avoid data races and parameter life-time issues. In cases
> > I don't know exactly whether
> > the function takes its parameter by value or by ref-to-const I
> > need to do that as a precaution.
>
> If you don't know the prototype of a function you are calling, well,
> that's just scary.

Think generic. In many functions you can replace pass-by-value with
pass-by-ref-to-const and the other way around and it doesn't make a
big difference -- in the single-threaded case that is. From what I
understand it would make a big difference in your proposal compared to
the single-threaded case.

> > an advantage in terms of readability. The only rule one has to
> > teach is: "Anything you forward with a ref-wrapper needs explicit
> > synchronization. Anything else is copied and safe without
> > explicit synchronization". Simple rule. I like it.
>
> You shouldn't. It's wrong.

How convenient for you to say that. In case you're thinking of
pointers: You can certainly copy a pointer to a new thread, print the
address it is holding to cout, do some pointer arithmetic ... all
fine. No data races. No life-time issues. The pointer is not going
to disappear just like that. *Dereferencing* it a different story --
obviously. I admit that I should have included a sentence about
pointers. It's probably NOT obvious to people who are new to
programming. So, a rule of thumb about synchronization should
probably cover it.

> My design offers consistency: parameter passing always has the same
> semantics, everything you know about parameter-passing from
> single-threaded programming continues to apply, and you are always and
> completely responsible for addressing lifetime issues and data races.

We disagree on it being the same concept where one thing needs to
approximate the other. In a single-threaded environment there are no
data races and when a functions accepts a parameter by ref-to-const or
by value it doesn't suddenly cease to exist or change its state.

> Your design offers a hodge-podge of special-cases rules that
> sometimes prevent races and sometimes do not (cases 5-6), that
> sometimes break traditional semantics (case 3) and sometimes do not.

No special cases. The make_tuple concept is a rather simple one.
Check the lambda/tuple-forwarding comparison from above.

Your proposal isn't anymore "consistent". How can anything be
consistent with the single-threaded case and not introduce multiple
pitfalls due to the asynchronicity? I also don't see how case 5-6 are
supposed to make "T-semantics" look [insert any negative attribute you
mentioned here].

> C++ is already a big, complicated, messy language. Your semantics
> make it bigger, more complicated, and messier.

While we're at it, let's throw tuples out of the proposal. C++ is
already complicated enough. ;-)

Seriously, that's just your point of view. I don't try to
axpproximate the semantics of synchronous function calls in
std::thread and I have -- in my opinion -- good reasons not to do
that. I'm saying the variadic constructor doesn't need to behave like
you want it to because it is a different concept (asynchronous) and
some/many would not even expect that kind of behaviour you desire (me
included). I personally find it "more natural" with "T-semantics".
Anyhow .... statements like these ("bigger, more complicated,
messier", "more natural") which are seemingly unfounded in the eyes of
"the opponents" are not going to further this discussion. Let's stick
to use cases and potential user errors.

Maybe another person could chime in and offer a third perspective.
For example, I'd be interested in what the semantics of the current
proposal is supposed to be. I can only guess -- judging by the
requirements (lvalues have to be copy-constructible, rvalues have to
be move-constructible) that it's at least similar to the "tuple-
forwarding" idea.

Cheers!
SG

Scott Meyers

unread,
Jul 10, 2009, 4:26:12 AM7/10/09
to
Bart van Ingen Schenau wrote:
>
> At first glance, there are no problems with this code and with R-
> semantics the compiler will accept it as well. So, is the code correct?
> No, it is not, because the dot_product function receives dangling
> references to the vectors (remember that matrix::get_row and
> matrix::get_col return a temporary object).
> The fix for this issue requires that we store all those vectors to
> ensure they live long enough. This could add quite an overhead to an
> otherwise simple algorithm.

This is when a caller uses bind, which does (at user request) exactly
what you want the library to do silently. There is no reason to
believe the overhead of using bind will be any greater than the
overhead of T semantics.

What do you do if the functions you want to call uses pointer
parameters and return values instead of references, e.g., if you are
calling into a library with a C API?

You're not solving the general problem, you're just papering over what
you consider to be a common case. If it is a common case, your
approach has the interesting side effect that it trains programmers to
believe that that they don't need to worry about lifetimes and races,
when in fact they certainly do.

Scott

--

Anthony Williams

unread,
Jul 10, 2009, 3:30:22 PM7/10/09
to
Scott Meyers <use...@aristeia.com> writes:

> Anthony Williams wrote:
>> I think the technical difficulties in such an implementation outweigh
>> the benefits. For one thing it *does* make it impossible to write this
>> part of the library in plain C++. For another, it means special-casing a
>> particular scenario in the compiler. It means that the one invocation of
>> foo that happens to be called as the thread function for a new thread
>> has to have different argument-passing semantics to all other calls to
>> foo, so that the compiler can notify the std::thread constructor that
>> the arguments are correctly constructed.
>
> I think not. Consider the binary code generated from a function taking a
> by-reference parameter. Almost certainly that parameter is actually passed by
> pointer. That is, you write this in the source code,
>
> void f(T& param);
>
> and the underlying binary looks like this:
>
> void f(T* param);
>
> Assuming this is true, all the back end of the compiler has to do is treat
> by-reference parameters like by-pointer parameters *in all cases*, which I'm
> fairly certain is what it already does. The current semantics of thread
> construction simply call for passing the address of a temporary, while I'm
> advocating passing the address of whatever argument is passed in, i.e., the
> exact same semantics you get with a synchronous function call.

That's not what I mean. I mean that in a normal call to f, the compiler
is free to pass some parameters on the stack, and others in registers,
and even just pass a pointer to the parameter info, which f then uses to
construct the parameters.

In order for the compiler to notify the std::thread constructor that f's
parameters have been constructed properly, the compiler must now insert
a "notify parameter construction complete" call *after* initializing the
parameters. How does it know to do this?

> As for "special-casing a particular scenario," have you not noticed
> that this is
> what I'm trying to get rid of? The current spec says "a pass-by-reference
> parameter means blah blah blah, except when it's done through a thread
> constructor, in which case the following contradictory special rule kicks in."

std::bind works the same way: std::bind(f,param) will *copy* param, even
if f takes a reference. std::thread works just like bind in this respect
=> consistency.

There is a fundamental difference between

std::thread t(f,param);

and

f(param);

They are just not the same: the syntax is different and the effects are
different. I don't see that it's that hard for people to learn that
std::thread always copies its parameters, and you need to explicitly
pass things using std::ref if you want a reference.

Incidentally, what would you do for:

void f(std::string const& s);
std::thread t(f,"hello");

or any other case of implicit conversion (possibly to a reference)?
Would you do the implicit conversion in the constructor, and pass a
reference (possibly to a temporary that will go out of scope when the
constructor finishes)?

>> std::thread can be constructed with callable objects, which can have
>> overloaded function call operators.
>>
>> struct foo
>> {
>> template<typename T>
>> void operator()(T) const;
>>
>> template<typename T,typename U>
>> void operator()(T&,U&) const;
>>
>> int operator()(int,std::string const&) const;
>> };
>
> Valid point. With luck, the proposal in N2901 to eliminate the variadic thread
> constructor will be adopted, and then this becomes a non-issue.

I really hope that proposal will NOT be adopted. Even with lambdas I
think the variadic constructor provides a cleaner syntax for launching
threads in many cases.

> Given
> that most
> functions taking function objects as parameters are not variadic (e.g.,
> condition_variable::wait, the entire STL), having thread construction take only
> a function object (but not any arguments for it) would bring it into uniformity
> with the rest of the standard library.
>
> Not that it matters, but given what I consider to be fundamentally broken
> semantics of pass-by-reference via variadic thread construction in the draft
> standard, my plan is to encourage programmers to use only lambdas, anyway.

I would just advise against using pass-by-reference with thread
functions. It's too easy to get the synchronization wrong.

You have repeatedly asked about pointers. I am not concerned about
pointers. If I have a variable v, I cannot pass a pointer to that
variable by accident: I have to pass &v. If I already have a pointer, I
can safely pass that by value; I still need to synchronize access
through the pointer, but that's different --- I have a pointer in my
thread function, so I know that's the case. On the other hand, I can
pass v to a function by reference without noticing at the call site ---
it's the same syntax as pass-by-value. This is not safe for threading,
since you need to be aware of when the passed data might be accessed by
the thread and when it cannot be.

Anthony
--
Author of C++ Concurrency in Action | http://www.manning.com/williams
just::thread C++0x thread library | http://www.stdthread.co.uk
Just Software Solutions Ltd | http://www.justsoftwaresolutions.co.uk
15 Carrallack Mews, St Just, Cornwall, TR19 7UL, UK. Company No. 5478976

[ comp.std.c++ is moderated. To submit articles, try just posting with ]

Scott Meyers

unread,
Jul 10, 2009, 3:31:17 PM7/10/09
to
SG wrote:
> struct user_type { int i; double d; };
>
> void foo(ud_type k, string const& text) {
> cout << text << k.i; // <--- garbage?
> // data race for text, for k, or both?
> }
>
> thread launch(int i, string const& x)
> {
> user_type u = {i, 3.14};
> return thread(foo,u,x);
> }
>
> int main() {
> vector<thread> tv;
> for (int i=0; i<10; ++i)
> tv.push_back(launch(i,"hello"));
> for (auto& t : tv)
> t.join();
> }

Either foo expects to get a reference to an object that may change state over
the lifetime of the call to foo, or it does not. This is part of its
interface,
and callers must pay attention to it. If foo does expect to get such a
reference, that becomes part of the interface for launch (i.e., the object to
which x refers must have a lifetime that extends at least to the point
where foo
has completed), and callers of launch are responsible for making sure that
whatever they pass has a sufficient lifetime. In the code above, this would
mean that the call to launch is broken, because the caller is allowing a
temporary to be created, and that temporary will not have sufficient lifetime.
In that case, the call to launch would fail to satisfy launch's preconditions.
(The calling code is broken, but it will compile.)

If foo does not expect text to change state during execution of foo, then foo's
caller (launch) is still responsible for making sure that the object to which x
refers will exist during execution of foo. This lifetime requirement does not
become part of launch's interface, however, meaning that callers of launch are
free to pass rvalues to bind to x.

Because launch cannot know the lifetime of the object referred to by x, it must
make a copy that is guaranteed to have a sufficient lifetime. So:

return thread(std::bind(foo, u, x));

> I didn't. I specifically addressed it in my text "case 4". I don't
> consider this an issue. If you forget ref() in this case the code
> won't compile. Nice, isn't it?

This is the crux of our disagreement. I don't think it's nice. I'm calling a
function that takes a by-reference parameter. I'm passing an lvalue. I
shouldn't have to say "pass this by reference" at the call site when the
function I'm calling already says "this parameter will be passed by reference."

> Think generic. In many functions you can replace pass-by-value with
> pass-by-ref-to-const and the other way around and it doesn't make a
> big difference -- in the single-threaded case that is. From what I
> understand it would make a big difference in your proposal compared to
> the single-threaded case.

That's correct. I find it interesting that we agree that writing MT code is
fundamentally different from writing ST code, yet we want to preserve different
things about writing ST code. I want argument-passing to remain the same. You
don't. You want to preserve "pass by value is pretty much the same as pass by
reference." That's not a priority for me, because I think that value semantics
are fundamentally different from reference semantics, and the
difference becomes
more important in the presence of multiple threads of execution.

> pointers: You can certainly copy a pointer to a new thread, print the
> address it is holding to cout, do some pointer arithmetic ... all
> fine. No data races. No life-time issues. The pointer is not going
> to disappear just like that. *Dereferencing* it a different story --

Which is why I kept bringing it up. IME, there are not a lot of functions that
take pointer parameters and never dereference them.

It's clear that we have a fundamental disagreement. We agree, I think, that
asynchronous function execution is fundamentally different from synchronous
function execution. You believe that because of this fundamental difference,
the argument-passing semantics for asynchronous calls should be different from
those for synchronous calls. I don't. I believe that argument-passing
semantics for all calls -- synchronous and asychronous -- should be
the same. I
don't think either of us is likely to convince the other to change
their position.

The committee meets next week, and I know that there is at least one
proposal to
get rid of the variadic thread constructor. It will be interesting to see
whether that proposal is adopted. There may well be consideration of
modifications to lambda semantics, too, although I don't know of (and have not
searched for) any proposals that would enable lambdas to handle move-only
arguments. Once the meeting is over, the landscape wrt thread invocation may
look different than it does now.

Scott

Scott Meyers

unread,
Jul 11, 2009, 2:02:24 AM7/11/09
to
Anthony Williams wrote:
> In order for the compiler to notify the std::thread constructor that f's
> parameters have been constructed properly, the compiler must now insert
> a "notify parameter construction complete" call *after* initializing the
> parameters. How does it know to do this?

I don't know that it does. I'm happy to admit that I don't know enough
about
the underlying mechanics of starting new threads, telling them what
function to
execute, and passing parameters to that function to describe in detail
how to
implement things. N�ively, I'd expect that something like the following
would work:

- Enter the std::thread constructor.
- Tell the OS to set up a stack for a new thread.
- Initialize the parameters for f in the new stack space.
- Tell the OS to asynchronously run f using the new stack. (The code
generated for f knows where on its stack its parameters are located.)
- Return from the thread constructor.

Even if this is totally wrong, I would be truly stunned were it to turn
out to
be the case that the semantics I'm advocating cannot be practically
implemented.

> std::bind works the same way: std::bind(f,param) will *copy* param, even
> if f takes a reference. std::thread works just like bind in this respect
> => consistency.

bind *inherently* produces a function object: its return value. It
doesn't
invoke anything -- certainly not the function object it is passed. One can
argue about whether bind should make a copy by default (in this sense, it's
almost amusing that a lambda expression, which essentially does the same
thing,
has no default), but asking bind to create a function object is, at
least to me,
fundamentally different from asking for a function to be invoked.

When I call bind, I'm asking for my arguments to be stored inside the
resulting
function object -- the function object *that must exist*. When I call a
function, I'm asking for my arguments to be used to initialize the
function's
parameters. I'm not asking for any intermediate object to be created,
and I
(speaking for me personally) am surprised that one sometimes will be.
(To be
more precise, "sometimes will be" in cases where no such object would be
created
in a synchronous call. Synchronous calls sometimes create intermediate
objects,
e.g., to effect implicit type conversions.)

> There is a fundamental difference between
>
> std::thread t(f,param);
>
> and
>
> f(param);
>
> They are just not the same: the syntax is different and the effects are
> different. I don't see that it's that hard for people to learn that
> std::thread always copies its parameters, and you need to explicitly
> pass things using std::ref if you want a reference.

People can learn it either way. The question is: what do you want them
to have
to learn? I argue that no matter what you do with parameter passing,
people
writing MT programs have to learn to be sensitive to object lifetimes
and race
conditions, and they have to learn how to program to avoid them. They
do NOT
have to learn that "pass by reference doesn't really mean pass by
reference when
you invoke a function to run on a different thread." That's optional.
I prefer
not to burden them with it.

> Incidentally, what would you do for:
>
> void f(std::string const& s);
> std::thread t(f,"hello");
>
> or any other case of implicit conversion (possibly to a reference)?

I addressed this in my response earlier today to SG, so I won't repeat
it here.

> I really hope that proposal will NOT be adopted. Even with lambdas I
> think the variadic constructor provides a cleaner syntax for launching
> threads in many cases.

We'll see what happens next week. My guess is that you'll be there to
advocate
for your position. I will not be. My money's on you getting your way :-)
[I recognize that it's not just "your" way. Clearly many people agree
with you.]

> I would just advise against using pass-by-reference with thread
> functions. It's too easy to get the synchronization wrong.

This strikes me as perfectly reasonable advice, and the nice thing about
it is
that it's completely independent of how parameters are passed.

> can safely pass that by value; I still need to synchronize access
> through the pointer, but that's different --- I have a pointer in my
> thread function, so I know that's the case. On the other hand, I can
> pass v to a function by reference without noticing at the call site ---
> it's the same syntax as pass-by-value. This is not safe for threading,
> since you need to be aware of when the passed data might be accessed by
> the thread and when it cannot be.

I agree, but I also believe that if you are not aware of the interface
of the
function you are calling, you're in serious trouble. If you're calling a
function and you're dealing with a parameter with reference semantics in
any
capacity (e.g., passing a raw pointer, a smart pointer, a reference, a
handle
with reference semantics, etc.), you have to be aware of synchronization
issues.
If the way you determine whether you need to worry about that is by
looking to
see if there's a "&" at the call site, this is probably not the language
you
should be using.

Scott


--

SG

unread,
Jul 11, 2009, 2:01:47 AM7/11/09
to
Scott Meyers:

> SG wrote:
> > struct user_type { int i; double d; };
>
> > void foo(ud_type k, string const& text) {
> > cout << text << k.i; // <--- garbage?
> > // data race for text, for k, or both?
> > }
>
> > thread launch(int i, string const& x)
> > {
> > user_type u = {i, 3.14};
> > return thread(foo,u,x);
> > }
>
> > int main() {
> > vector<thread> tv;
> > for (int i=0; i<10; ++i)
> > tv.push_back(launch(i,"hello"));
> > for (auto& t : tv)
> > t.join();
> > }
>
> [...]

> The calling code is broken, but it will compile.
> [...]

> Because launch cannot know the lifetime of the object referred to by x, it must
> make a copy that is guaranteed to have a sufficient lifetime. So:
>
> return thread(std::bind(foo, u, x));

Ok, I got it. Thanks for the explanation. But don't you think that
your proposed semantics makes this variadic template "mostly useless"
if I have to use bind in probably 95% of the cases?

> > [...]


> This is the crux of our disagreement. I don't think it's nice. I'm calling a
> function that takes a by-reference parameter. I'm passing an lvalue. I
> shouldn't have to say "pass this by reference" at the call site when the

> [...]


> I want argument-passing to remain the same. You
> don't. You want to preserve "pass by value is pretty much the same as pass by

> reference." [...]

Well, you don't "call a function". You're constructing a thread
object. This has been my point all along. Different concept, different
syntax, and different semantics. This is where our perspectives
differ. You see this as a function call. I don't. I'd like to be able
to instruct std::thread on how to forward parameters. Why should I
have to use lambdas or std::bind and decrease the signal-to-noise
ratio? They don't even support move-only types.

> > [...]


> > fine. No data races. No life-time issues. The pointer is not going
> > to disappear just like that. *Dereferencing* it a different story --
> Which is why I kept bringing it up. IME, there are not a lot of functions that
> take pointer parameters and never dereference them.

True. But one doesn't take an address by accident. You have to use the
& operator for that. If you do and you're not aware of its
implications ... tough luck.

> I don't think either of us is likely to convince the other to
> change their position.

I think so, too.

> The committee meets next week, and I know that there is at least
> one proposal to get rid of the variadic thread constructor.

The justification is, however, different from yours.

> It will
> be interesting to see whether that proposal is adopted. There may
> well be consideration of modifications to lambda semantics, too,
> although I don't know of (and have not searched for) any proposals
> that would enable lambdas to handle move-only arguments. Once the
> meeting is over, the landscape wrt thread invocation may look
> different than it does now.

Good closing words. -- Time for me to shup up.

Cheers!
SG

Bart van Ingen Schenau

unread,
Jul 11, 2009, 12:29:41 PM7/11/09
to
Scott Meyers wrote:

> Bart van Ingen Schenau wrote:
>>
>> At first glance, there are no problems with this code and with R-
>> semantics the compiler will accept it as well. So, is the code
>> correct? No, it is not, because the dot_product function receives
>> dangling references to the vectors (remember that matrix::get_row and
>> matrix::get_col return a temporary object).
>> The fix for this issue requires that we store all those vectors to
>> ensure they live long enough. This could add quite an overhead to an
>> otherwise simple algorithm.
>
> This is when a caller uses bind, which does (at user request) exactly
> what you want the library to do silently. There is no reason to
> believe the overhead of using bind will be any greater than the
> overhead of T semantics.

But why should I explicitly create a function object with bind, when
creating a thread object does the same thing (plus throw it over the
wall to have it executed elsewhere).

Why do you insist that creating a thread is semantically the same as
calling a function?
thread t(foo, a);
is just as much a function call as
string s("hello");

To me, the syntax of creating a thread is much closer to creating a
functor with bind, so I expect the semantics to be similar as well.

If I were to explain the effects of
thread t(foo, a);
I would tell that it creates a functor like in 'bind(foo, a)' and causes
that functor to be called in a different thread.

>
> What do you do if the functions you want to call uses pointer
> parameters and return values instead of references, e.g., if you are
> calling into a library with a C API?

The moment I see an address-of operator while starting a thread, that is
a danger sign that the code has to be inspected extra carefully for
lifetime-issues of the pointed-to object.
My problem with the R-semantics is that there is no such danger sign
when a temporary might get bound to a reference.

If the R-semantics become standard, I would probably only write (and
only accept in code reviews) the form 'thread t(bind(/*...*/))', just
because of the issue with temporaries the the fact that the
interchangeability of 'pass-by-value' and 'pass-by-const-reference' is
too much ingrained in the programmers mind. I consider it likely that an
unwary maintenance programmer will 'optimise' some thread functions to
use pass-by-const-reference, even if the original programmer had
deliberately used pass-by-value to avoid dangling references.

>
> You're not solving the general problem, you're just papering over what
> you consider to be a common case. If it is a common case, your
> approach has the interesting side effect that it trains programmers to
> believe that that they don't need to worry about lifetimes and races,
> when in fact they certainly do.

As I see it, you are creating a gaping type-safety hole in the language,
without any danger signs.
Every programmer must consider, for every function, what would happen if
some day that function would be used as a thread-function. That
consideration gives a very big conflict with the common teaching (which
you also teach in item 22 of Effective C++ (2nd ed.)) that pass-by-
reference should be preferred over pass-by-value for efficiency reasons.

>
> Scott


>
Bart v Ingen Schenau
--
a.c.l.l.c-c++ FAQ: http://www.comeaucomputing.com/learn/faq
c.l.c FAQ: http://c-faq.com/
c.l.c++ FAQ: http://www.parashift.com/c++-faq-lite/

Felipe Magno de Almeida

unread,
Jul 11, 2009, 12:31:03 PM7/11/09
to
On Jul 11, 3:02 am, Scott Meyers <use...@aristeia.com> wrote:
> Anthony Williams wrote:

[snip]

> > std::bind works the same way: std::bind(f,param) will *copy* param, even
> > if f takes a reference. std::thread works just like bind in this respect
> > => consistency.
>
> bind *inherently* produces a function object: its return value. It
> doesn't
> invoke anything -- certainly not the function object it is passed. One can
> argue about whether bind should make a copy by default (in this sense, it's
> almost amusing that a lambda expression, which essentially does the same
> thing,
> has no default), but asking bind to create a function object is, at
> least to me,
> fundamentally different from asking for a function to be invoked.

Bind creates a function which is executed sometime in the future.
std::thread runs a function with a set of parameters sometime in the
future.
If std::thread constructor's doesn't guarantee when the thread will be
up and
running, then it seems to me that bind semantics are coherent.
Though I don't have strong feelings about requiring std::thread
constructor to guarantee
returning only after the arguments construction on the thread's stack.
Which I think would
allow the single-thread semantics of argument passing.

[snip]

> Scott
>
> --

--
Felipe Magno de Almeida

Scott Meyers

unread,
Jul 12, 2009, 4:17:14 PM7/12/09
to
Bart van Ingen Schenau wrote:
>
> As I see it, you are creating a gaping type-safety hole in the language,
> without any danger signs.

You may believe that what I'm advocating creates a safety hole, even a
gaping one, but it doesn't compromise the type system in any way.
Everything advocated by everybody in this thread is fully type-safe.

> Every programmer must consider, for every function, what would happen if
> some day that function would be used as a thread-function. That
> consideration gives a very big conflict with the common teaching (which
> you also teach in item 22 of Effective C++ (2nd ed.)) that pass-by-
> reference should be preferred over pass-by-value for efficiency reasons.

That advice -- and a whole lot more -- will have to be revisited for
C++0x. Common teaching changes (or at least should change) as the
environment does. Common teaching about well-formed functions changed
when people starting understanding the implications of exceptions on
correctness and resource leaks. Common teaching about "good" software
changed when it became more common to write MT programs. Certainly
common teaching about C++ will be revised in light of rvalue
references, lambda functions, auto-declared variables, range-based for
loops, variadic templates, uniform initialization syntax, delegating
and inheriting constructors, etc. For synchronous execution,
pass-by-value and pass-by-ref-to-const are pretty much synonymous.
For asynchronous execution, they aren't. When the language changes,
the rules change, too.

In essence, this thread boils down to what has changed about parameter
passing given C++0x's support for threads. I think everybody agrees
that there are new issues wrt lifetime and synchronization that
programmers must worry about, regardless of how parameters are passed.
I argue that passing arguments to functions synchronously and
asynchronously should be the same -- that this is something that need
not change. You and others argue that synchronous and asynchronous
calls are so fundamentally different that different parameter-passing
rules should apply.

>From the "old" rules you want to preserve the idea that pass-by-value
and pass-by-ref-to-const are pretty much the same. I don't. I want
to preserve the semantics of parameter passing. You don't. Neither
of us is right. Neither of us is wrong.

But of course I'm righter than you are :-)

Scott

--

Bart van Ingen Schenau

unread,
Jul 13, 2009, 8:44:51 PM7/13/09
to
Scott Meyers wrote:

> Bart van Ingen Schenau wrote:
>>
>> As I see it, you are creating a gaping type-safety hole in the
>> language, without any danger signs.
>
> You may believe that what I'm advocating creates a safety hole, even a
> gaping one, but it doesn't compromise the type system in any way.
> Everything advocated by everybody in this thread is fully type-safe.

That depends on the exact definition of 'type safety' you use.
In some definitions, returning a reference/pointer to a local/temporary
is also a type-safety issue. I was using the term 'type safety' in as in
that definition.

>
>> Every programmer must consider, for every function, what would happen
>> if some day that function would be used as a thread-function. That
>> consideration gives a very big conflict with the common teaching
>> (which you also teach in item 22 of Effective C++ (2nd ed.)) that
>> pass-by- reference should be preferred over pass-by-value for
>> efficiency reasons.
>
> That advice -- and a whole lot more -- will have to be revisited for
> C++0x. Common teaching changes (or at least should change) as the
> environment does.

As wisdom evolves, so do the common teachings. But until everybody has
caught up (in say 5 to 10 years), a lot of people will be writing broken
code.

<snip>


> In essence, this thread boils down to what has changed about parameter
> passing given C++0x's support for threads.

I don't agree. I think this thread boils down to how much safety are we
willing to exchange for semantics that (IMO) very few people use.

When changing the standard, it is not only important to keep it
backwards compatible with old code, but also to avoid invalidating
common wisdom completely.
Common wisdom is that pass-by-value and pass-by-const-reference can be
used interchangeably, but when you pass references as reference to a
thread function, that common wisdom ceases to be valid from one day to
another, without any warning. I predict that the effects will be nearly
as bad a silently breaking existing code.

> I think everybody agrees
> that there are new issues wrt lifetime and synchronization that
> programmers must worry about, regardless of how parameters are passed.
> I argue that passing arguments to functions synchronously and
> asynchronously should be the same -- that this is something that need
> not change. You and others argue that synchronous and asynchronous
> calls are so fundamentally different that different parameter-passing
> rules should apply.

That is not the whole of my argument.
Yes, synchronous and asynchronous calls are syntactically different
enough to justify different semantics, especially when you add delayed
calls (using bind) into the mix.

But the fundamental reason for wanting different semantics is safety. It
is just too easy to inadvertently make an asynchronous call with
parameters that don't live long enough.
At the very least, it must be made a diagnosable error to bind
references to temporary objects in an asynchronous call. But to keep
consistency within the standard, that error should then be extended to
cover references in all cases.

>
>>From the "old" rules you want to preserve the idea that pass-by-value
> and pass-by-ref-to-const are pretty much the same. I don't. I want
> to preserve the semantics of parameter passing. You don't. Neither
> of us is right. Neither of us is wrong.
>
> But of course I'm righter than you are :-)

dangling references good, unexpected copying bad ;-)

>
> Scott
>
Bart v Ingen Schenau
--
a.c.l.l.c-c++ FAQ: http://www.comeaucomputing.com/learn/faq
c.l.c FAQ: http://c-faq.com/
c.l.c++ FAQ: http://www.parashift.com/c++-faq-lite/

Peter Dimov

unread,
Jul 22, 2009, 1:58:27 PM7/22/09
to
On Jul 1, 11:37 am, Scott Meyers <use...@aristeia.com> wrote:

> What's wrong with passing an lvalue by reference?

I can answer this for bind.

1. Explicitly requesting pass by reference via ref is less error prone
than expecting bind to "do the right thing".

2. Doing the right thing is hard. Consider

struct F
{
void operator()( int ) const;
void operator()( long& ) const;
} f;

Now bind( f, x ) needs to capture by value or by reference depending
on the type of x.

As for why in bind( g, 0 ), g is allowed to modify the zero, this can
be used to create function objects with local state, in a call to
std::generate, for example.

int g( int & x ) { return ++x; }

generate( first, last, bind( g, 0 ) );

Now that we have lambdas there's less need for it, of course.


--


[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use

mailto:std...@netlab.cs.rpi.edu<std-c%2B%2...@netlab.cs.rpi.edu>

0 new messages