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

futures and packaged tasks

47 views
Skip to first unread message

Doug Mika

unread,
Aug 8, 2015, 5:38:30 PM8/8/15
to
Could anyone tell me why the following won't compile? To be honest, I don't know where to begin with the compiler error messages, even the first one.

Thanks

// Example program
#include <iostream>
#include <string>
#include <future>
#include <type_traits>

template<typename F, typename A>
std::future<std::result_of<F(A&&)>::type> spawn_task(F&& f, A&& a){
typedef std::result_of<F(A&&)>::type result_type;
std::packaged_task<result_type(A&&)> task(std::move(f)));
std::future<result_type> res(task.get_future());
std::thread t(std::move(task),std::move(a));
t.detach();
return res;
}

int cube(int x){
return x*x*x;
}

int main()
{
int myInt=3;
std::future<int> result = spawn_task(cube, myInt);
cout<<"The result is: "<<result.get()<<endl;
}


Alf P. Steinbach

unread,
Aug 9, 2015, 2:55:37 AM8/9/15
to
On 08-Aug-15 11:37 PM, Doug Mika wrote:
> Could anyone tell me why the following won't compile? To be honest, I don't know
> where to begin with the compiler error messages, even the first one.

So why do you omit the error messages, the compiler information, etc.?

Here's a FAQ item that guides about posting about this kind of issue:

"How do I post a question about code that doesn't work correctly?"
http://www.cs.rit.edu/~mjh/docs/c++-faq/how-to-post.html#faq-5.8

The link is to mirror site, that will probably soon disappear due to the
FAQ's migration to isocpp.org.

Unfortunately the C++ FAQ has only been partially migrated to
https://isocpp.org/faq, and the above item is one that as yet has no
text there, but still has the text removed over at parashift (original
FAQ residence). And as another example that the FAQ is not currently
complete, the original FAQ's item about newbie guides still directs the
reader to my old tutorial, which has been offline for umpteen years! But
in spite of such problems I do recommend checking out the FAQ, before
posting a question -- chances are it's already been answered, or that
the FAQ has some good advice about it or about posting... ;-)


> // Example program
> #include <iostream>
> #include <string>
> #include <future>
> #include <type_traits>
>
> template<typename F, typename A>
> std::future<std::result_of<F(A&&)>::type> spawn_task(F&& f, A&& a){
> typedef std::result_of<F(A&&)>::type result_type;
> std::packaged_task<result_type(A&&)> task(std::move(f)));
> std::future<result_type> res(task.get_future());
> std::thread t(std::move(task),std::move(a));
> t.detach();
> return res;
> }
>
> int cube(int x){
> return x*x*x;
> }
>
> int main()
> {
> int myInt=3;
> std::future<int> result = spawn_task(cube, myInt);
> cout<<"The result is: "<<result.get()<<endl;
> }

General advice: start with something simpler.

Cheers & hth.,

- Alf

--
Using Thunderbird as Usenet client, Eternal September as NNTP server.

Chris Vine

unread,
Aug 9, 2015, 6:02:20 AM8/9/15
to
On Sat, 8 Aug 2015 14:37:59 -0700 (PDT)
Doug Mika <doug...@gmail.com> wrote:
> Could anyone tell me why the following won't compile? To be honest,
> I don't know where to begin with the compiler error messages, even
> the first one.
[snip]

First, you need to learn how to construct a std::packaged_task object
of type std::packaged_task<int()> from your cube() function and its
int argument. Writing other superfluous code (which contains different
errors) before you have done that is unhelpful.

Furthermore, you have already been told how to construct such a
std::packaged_task object in a previous posting of yours - use a lambda
expression or std::bind. If you didn't understand the responses you
received, try reading them again and online reference material for
those.

Once you have done that try constructing a thread object from the
packaged_task object. Once you have done that, do more research on how
std::move and std::forward work.

Chris

Chris Vine

unread,
Aug 9, 2015, 6:15:28 AM8/9/15
to
I should add for completeness (and for your self-learning purposes) that
alternatively you can construct a std::packaged_task<int(int)> object
from your cube function in the way you proposed and then bind the
argument when constructing the thread object, but that will not be the
approach to adopt when writing your generic task queue: as you
recognised in your post under the heading "packaged_task", that
requires a std::packaged_task<T()> object.

Play around with it and you should get the hang of it.

Chris

Doug Mika

unread,
Aug 9, 2015, 2:34:16 PM8/9/15
to
I got this code from a book on threading in c++, and I found the code useful, but it doesn't compile on cpp.sh. Unfortunately I have not yet managed to read a book on templates, but I hope to get around to it soon enough. I was going to ask a follow up question: how I could change the code above to allow for functions which take more than one parameter. I guess the answer would help me decipher the workings of templates before I get to read a book on it.

Doug Mika

unread,
Aug 9, 2015, 3:20:59 PM8/9/15
to
I actually found some modified version of the function, and here it is:
// Example program
#include <iostream>
#include <string>
#include <future>
#include <type_traits>
#include <string>

template<typename F, typename A>
std::future<typename std::result_of<F(A)>::type> spawn_task(F&& f, A&& a){
typedef typename std::result_of<F(A)>::type result_type;
std::packaged_task<result_type(A)> task(std::move(f));
std::future<result_type> res(task.get_future());
std::thread t(std::move(task),std::move(a));
t.detach();
return res;
}

int cube(int x){
return x*x*x;
}

int main(){
int myInt=3;
auto result = spawn_task(cube, myInt);
std::cout<<"The result is: "<<result.get()<<std::endl;
}

The only problem now is with the creation of the thread t:
std::thread t(std::move(task),std::move(a));

Why does the compiler say the following: (and what does that exactly mean?)

required from 'std::future<typename std::result_of<F(A)>::type> spawn_task(F&&, A&&) [with F = int (&)(int); A = int&; typename std::result_of<F(A)>::type = int]'

Chris Vine

unread,
Aug 9, 2015, 4:10:47 PM8/9/15
to
The problem is that if the integer passed to spawn_task() is a lvalue,
as it is here, then A&& collapses to int& which causes instantiation
failure of the thread object when passed a std::packaged_task<int(int&)>
object. The other problem is that you are not using std::move correctly.
Here is a corrected version which deals with the issues with your code,
and makes some other clean-ups (you can use decltype here rather than
having to use std::result_of), in C++11 compatible form: some shortening
is possible with C++14. On std::move/std::forward, I suggest you read
up on what are sometimes called "universal" references, "collapsible"
references or "forwarding" references. The code below is tested with
gcc-5.1 and clang-3.6.0.

As I have mentioned elsewhere, I would not do it this way. I would use
std::bind to construct a std::packaged_task<T()> object, which saves
having to futz around with std::remove_reference.

Note also that if you were to use std::result_of, you need to apply
'typename' to the std::result_of<...>::type expression because it is
a dependent type (see also the use of 'typename' with
std::remove_reference below).


#include <iostream>
#include <string>
#include <future>
#include <type_traits>


template<typename F, typename A>
auto spawn_task(F&& f, A&& a) -> std::future<decltype(f(a))> {
typedef decltype(f(a)) result_type;
std::packaged_task<result_type(typename std::remove_reference<A>::type)> task{std::forward<F>(f)};
std::future<result_type> res{task.get_future()};
std::thread t{std::move(task), std::forward<A>(a)};
t.detach();
return res;
}

int cube(int x){
return x*x*x;
}

int main()
{
int myInt=3;
std::future<int> result = spawn_task(cube, myInt);

Doug Mika

unread,
Aug 9, 2015, 5:55:53 PM8/9/15
to
Much thanks for the corrected version, appreciated. I did manage to get mine to work, all that was needed was a std::move in the call to spawn thread. Reason probably as you explained. And I guess the instantiation failure is because passing reference parameters in function run in threads requires std::ref?

I was wondering where you could learn these things. I got this example from a book, but it certainly did not explain, line by line, what each line did. And it was buggy on top of that. I mean, the error message the compiler threw at me, well, I didn't know what the problem was in the least.

Chris Vine

unread,
Aug 9, 2015, 7:14:40 PM8/9/15
to
On Sun, 9 Aug 2015 14:55:32 -0700 (PDT)
Doug Mika <doug...@gmail.com> wrote:
[snip]
> Much thanks for the corrected version, appreciated. I did manage to
> get mine to work, all that was needed was a std::move in the call to
> spawn thread. Reason probably as you explained. And I guess the
> instantiation failure is because passing reference parameters in
> function run in threads requires std::ref?
>
> I was wondering where you could learn these things. I got this
> example from a book, but it certainly did not explain, line by line,
> what each line did. And it was buggy on top of that. I mean, the
> error message the compiler threw at me, well, I didn't know what the
> problem was in the least.

It works if you pass std::move(myInt) to spawn_task() because that
converts the lvalue to a rvalue and this causes A in spawn_task() to
be deduced as int, which works. Obviously however, requiring the
argument to be passed only as an rvalue is completely wrong: it "works"
for that particular case only as a side effect of a defective design.
As you suggest, it would also compile for lvalues if you passed the
argument to std::thread's constructor via std::ref. However that would
also be completely wrong for this usage - first, it would no longer work
for rvalue arguments, and secondly you should almost never pass an
argument to std::thread by reference rather than by value because the
variable passed may be out of scope by the time it is accessed by the
thread concerned. (The last point would not be true here though because
your code blocks on std::future::get() within the scope of the int
argument, but that is just an artifact of your simplistic example.)

It is difficult to believe that the code you have posted is actually in
a published book. Perhaps you are "converting" C++98 code using boost
into C++11 code using the standard library, but not doing so
correctly - C++98 does not have rvalue references, which means that it
does not have std::move, std::forward or collapsible references. If the
code really was in a book in the form in which you have presented it,
throw it in the bin.

This is basic stuff about lvalue and rvalue types, perfect forwarding
and reference collapsing, and is not specific to threads. Any decent
book which purports to cover C++11 should deal with it. The other
point to make is that this is normally only an issue for library
writers. Your code is redundant by virtue of std::async.

Chris

Doug Mika

unread,
Aug 9, 2015, 10:11:00 PM8/9/15
to
It's Listing 4.14 in C++ Concurrency in Action practical multithreading. It's not an easy read, but some examples seem useful. Any suggestion on a good intro to intermediate level book for C++ multithreading? Anyone? (of course in C++11)

Chris Vine

unread,
Aug 10, 2015, 6:10:53 AM8/10/15
to
On Sun, 9 Aug 2015 19:10:43 -0700 (PDT)
Doug Mika <doug...@gmail.com> wrote:
[snip]
> It's Listing 4.14 in C++ Concurrency in Action practical
> multithreading. It's not an easy read, but some examples seem
> useful. Any suggestion on a good intro to intermediate level book
> for C++ multithreading? Anyone? (of course in C++11)

Although I have never read it, I have heard quite good things about that
book so it is a bit worrying that it should come up with a code listing
which not only does not compile but which shows such a lack of
understanding of rvalue references, move semantics and template type
deduction. Anyway, you now have a corrected version from me. I
suggest you submit an erratum to the publisher because as I say I have
heard that the book is reasonably good on threading matters.

To be honest though, spending your time looking at a book about
multi-threading is probably not wise for someone who has yet to acquire
the basics. If you want to operate at this low level, you need to
understand rvalue references and template type deduction to understand
the code, and not just the particulars about std::packaged_task and
std::future. The basic point is that with this function signature for
perfect forwarding:

template <class T> void func(T&& t) {...}

T deduces as a reference type if passed a lvalue and a value type if
passed a rvalue. That's how forwarding works. But it does mean that
you cannot use T in the same way as you could in the C++98 days if the
signature were, say

template <class T> void func(T& t) {...}

which works completely differently - here func can only take a lvalue
and T deduces as the underlying value type, either const or non-const.

template <class T> void func(T t) {...}

does similarly, except that it discards const qualifiers and will
accept rvalues as well as lvalues (and a signature of func(const T& t)
would do the same but would differ in its treatment of volatile).

The literature on this is not that good in my opinion. Section 23.5.2
and 23.5.2.1 of Stroustrup's the C++ Programming Language (4th ed) is
unnecessarily light in content in my view. The following link is a
great deal more complete, but consequently more difficult for a
beginner to understand:
http://en.cppreference.com/w/cpp/language/template_argument_deduction

Part of the problem is probably that in C++ the use of && (rvalue
reference) has been overloaded to cover perfect forwarding by a sleight
of hand involving reference collapsing. It might have been thought
cute at the time but it is highly confusing for beginners (and also for
others, it would appear).

I also repeat the point I made before. Most C++ users don't actually
need to know about the details of template type deduction. Mostly it
just works as you would expect. But be on your guard whenever you see a
forwarding reference, where sometimes it does not.

Chris
0 new messages