Result type erased packaged_task

191 views
Skip to first unread message

Sergey Vidyuk

unread,
Feb 19, 2017, 11:49:41 PM2/19/17
to ISO C++ Standard - Future Proposals
Sometimes it's required to store std::packaged_task instances with the same argumet types but different result type in the same collection (especially when implementing your own executor-like class). I've started to work on a proposal to provide partial specialization for the std::packaged_task class template which erases result type for the task. The text is here: https://github.com/VestniK/portable_concurrency/blob/result-erased-task-proposal/proposal/proposal.tex pdf version is in attachement.

Github repository provides reference implemetntation for the proposed idea. My implementation uses the same design for the packaged_task as gcc and boost implementations (shared state erases wrapped functor type). Those two implementations can be adjusted to have proposed feature quite easily. Clang implementation requires some extra work since they store type erasure for wrapped function and promise. I haven't investigated Visual Studio implementation so don't know if it's hard to add proposed feature to their standard library.

I would like to ask for feedback on the proposed idea and the document text.

Sergey Vidyuk
result-erased-packaged-task.pdf

Bryce Glover

unread,
Feb 20, 2017, 5:34:55 PM2/20/17
to Sergey Vidyuk, std-pr...@isocpp.org
     You might want to run your proposal through a spelling and grammar checker, as I can see at first glance that at least the title is misspelled; ‘possibility’ has two ’s'es in it.  It also looks like your formatting’s somewhat scrambled with respect to font usage on page 4.  

Regards, 
     Bryce Glover

Arthur O'Dwyer

unread,
Feb 20, 2017, 7:03:42 PM2/20/17
to ISO C++ Standard - Future Proposals
(I agree that there's something wonky with the formatting.)

I don't understand why we couldn't solve this problem more simply just by making std::function<void(A...)> constructible from std::function<R(A...)>&&, std::future<void> constructible from std::future<T>&&, std::packaged_task<void(A...)> constructible from std::packaged_task<R(A...)>&&, and so on (just in case I missed any).

There might be a good reason for not doing that — e.g. it might subvert the type system too much, or it might introduce inefficiency behind the scenes — but I would want to see that you'd considered the possibility and rejected it for reasons, and weren't proposing a new tag type out of "laziness".

My intuition is that

    std::packaged_task<int()> pti( [](){ return 42; } );
    std::future<int> fi = pti.get_future();
    std::packaged_task<void()> ptv = std::move(pti);

ought to be legal, IMHO, because its meaning is "obvious"; it's equivalent to

    std::packaged_task<void()> ptv( [pti_ = std::move(pti)](){ pti_(); } );

Even

    std::packaged_task<int()> pti( [](){ return 42; } );
    std::packaged_task<void()> ptv = std::move(pti);
    std::future<void> fv = ptv.get_future();

has an "obvious" meaning IMHO, and I'd like to see it supported if it helps use-cases like Sergey's.

C++ already allows implicit conversion of std::function<T(A...)> to std::function<U(A...)> in exactly the cases I'd consider it appropriate. C++ just doesn't allow it for std::future or std::packaged_task. This suggests to me that the reason is inefficiency-behind-the-scenes, but I don't know if that's an insurmountable obstacle or just nobody's thought up and proposed an efficient solution yet.

If I'm missing something and your tag type ignore_t isn't basically interchangeable with void, please let me know. :)

–Arthur

Sergey Vidyuk

unread,
Feb 20, 2017, 9:22:22 PM2/20/17
to ISO C++ Standard - Future Proposals


вторник, 21 февраля 2017 г., 7:03:42 UTC+7 пользователь Arthur O'Dwyer написал:
On Sunday, February 19, 2017 at 8:49:41 PM UTC-8, Sergey Vidyuk wrote:
Sometimes it's required to store std::packaged_task instances with the same argumet types but different result type in the same collection (especially when implementing your own executor-like class). I've started to work on a proposal to provide partial specialization for the std::packaged_task class template which erases result type for the task. The text is here: https://github.com/VestniK/portable_concurrency/blob/result-erased-task-proposal/proposal/proposal.tex pdf version is in attachement.

Github repository provides reference implemetntation for the proposed idea. My implementation uses the same design for the packaged_task as gcc and boost implementations (shared state erases wrapped functor type). Those two implementations can be adjusted to have proposed feature quite easily. Clang implementation requires some extra work since they store type erasure for wrapped function and promise. I haven't investigated Visual Studio implementation so don't know if it's hard to add proposed feature to their standard library.

I would like to ask for feedback on the proposed idea and the document text.

(I agree that there's something wonky with the formatting.)

I don't understand why we couldn't solve this problem more simply just by making std::function<void(A...)> constructible from std::function<R(A...)>&&, std::future<void> constructible from std::future<T>&&, std::packaged_task<void(A...)> constructible from std::packaged_task<R(A...)>&&, and so on (just in case I missed any).

There might be a good reason for not doing that — e.g. it might subvert the type system too much, or it might introduce inefficiency behind the scenes — but I would want to see that you'd considered the possibility and rejected it for reasons, and weren't proposing a new tag type out of "laziness".

My intuition is that

    std::packaged_task<int()> pti( [](){ return 42; } );
    std::future<int> fi = pti.get_future();
    std::packaged_task<void()> ptv = std::move(pti);

ought to be legal, IMHO, because its meaning is "obvious"; it's equivalent to

    std::packaged_task<void()> ptv( [pti_ = std::move(pti)](){ pti_(); } );

Even

    std::packaged_task<int()> pti( [](){ return 42; } );
    std::packaged_task<void()> ptv = std::move(pti);
    std::future<void> fv = ptv.get_future();

has an "obvious" meaning IMHO, and I'd like to see it supported if it helps use-cases like Sergey's.


packaged_task is not just type-erasure for function. It's write end of the single-value thread-safe communication channel with capabilities to notify read channel about value delivery combined together with type-erasure for function with additional protection from calling wrapped function twice. Using it like std::function just because you want to store move-only function object involves 1 (or 2 in case of libc++) extra allocation, 1 extra indirection, thread-safe check if shared state is empty before the call, thread-safe operation of storing a value in the shared state.
 
C++ already allows implicit conversion of std::function<T(A...)> to std::function<U(A...)> in exactly the cases I'd consider it appropriate. C++ just doesn't allow it for std::future or std::packaged_task. This suggests to me that the reason is inefficiency-behind-the-scenes, but I don't know if that's an insurmountable obstacle or just nobody's thought up and proposed an efficient solution yet.

If I'm missing something and your tag type ignore_t isn't basically interchangeable with void, please let me know. :)

–Arthur

The main motivation of this proposal is to provide building block to implement executors (especially when this concept will be published with Concurrency TS 2). For example Implementation of TwoWayExecutor (p0443r1 terminology) which uses std::future for communication channel requires to pack together shared state and function to be executed, take a future from it and erase the result type of the packaged task in order to store it in the execution context till the time it will be executed. My proposal is to provide zero cost way to erase result type of the packaged_task. There are no ways to write such type erasure in the user code right now (any user code implementation will not be zero cost).

Sergey

Sergey Vidyuk

unread,
Feb 20, 2017, 11:22:40 PM2/20/17
to ISO C++ Standard - Future Proposals, sir.v...@gmail.com


вторник, 21 февраля 2017 г., 5:34:55 UTC+7 пользователь Bryce Glover написал:

Spellchecker find terrible mistakes in my text :( attaching fixed version.

What's wrong with formatting on the page 4? The only thing I can see is template arguments for the intext std::packaged_task<R(A...)> looks like R(A...) use different font size. Is the issue you are talking about?

Sergey
proposal.pdf

Bryce Glover

unread,
Feb 21, 2017, 7:26:35 PM2/21/17
to Sergey Vidyuk, std-pr...@isocpp.org
Sergey Vidyuk <sir.v...@gmail.com>: Feb 20 08:22PM -0800 


вторник, 21 февраля 2017 г., 5:34:55 UTC+7 пользователь Bryce Glover 
написал:
 
> Regards, 
> Bryce Glover
> Random...@gmail.com <javascript:>

 
Spellchecker find terrible mistakes in my text :( attaching fixed version.
 
What's wrong with formatting on the page 4? The only thing I can see is 
template arguments for the intext std::packaged_task<R(A...)> looks like 
R(A...) use different font size. Is the issue you are talking about?
 
Sergey

Dear Sergey, 

     The issue is that most of page 4 appears to switch between using different fonts (or just font sizes…?) at random, which could definitely cause readers mild confusion.  In particular, it looks like either a font one would use for code comments got interleaved into the text’s formatting or the font size just keeps changing at what appears to be random intervals of space.  This has possible consequences for readability when compared with the majority of the body text in your proposal.  

Hope that clarifies things, 
     Bryce Glover

Arthur O'Dwyer

unread,
Feb 21, 2017, 8:19:08 PM2/21/17
to ISO C++ Standard - Future Proposals, Sergey Vidyuk
On Mon, Feb 20, 2017 at 6:22 PM, Sergey Vidyuk <sir.v...@gmail.com> wrote:
вторник, 21 февраля 2017 г., 7:03:42 UTC+7 пользователь Arthur O'Dwyer написал:
On Sunday, February 19, 2017 at 8:49:41 PM UTC-8, Sergey Vidyuk wrote:
Sometimes it's required to store std::packaged_task instances with the same argumet types but different result type in the same collection (especially when implementing your own executor-like class). I've started to work on a proposal to provide partial specialization for the std::packaged_task class template which erases result type for the task. The text is here: https://github.com/VestniK/portable_concurrency/blob/result-erased-task-proposal/proposal/proposal.tex pdf version is in attachement. [...]

I don't understand why we couldn't solve this problem more simply just by making std::function<void(A...)> constructible from std::function<R(A...)>&&, std::future<void> constructible from std::future<T>&&, std::packaged_task<void(A...)> constructible from std::packaged_task<R(A...)>&&, and so on [...]
My intuition is that

    std::packaged_task<int()> pti( [](){ return 42; } );
    std::future<int> fi = pti.get_future();
    std::packaged_task<void()> ptv = std::move(pti);

ought to be legal, IMHO, because its meaning is "obvious"; it's equivalent to

    std::packaged_task<void()> ptv( [pti_ = std::move(pti)](){ pti_(); } );

Even

    std::packaged_task<int()> pti( [](){ return 42; } );
    std::packaged_task<void()> ptv = std::move(pti);
    std::future<void> fv = ptv.get_future();

has an "obvious" meaning IMHO, and I'd like to see it supported if it helps use-cases like Sergey's.

packaged_task is not just type-erasure for function. It's write end of the single-value thread-safe communication channel with capabilities to notify read channel about value delivery combined together with type-erasure for function with additional protection from calling wrapped function twice. Using it like std::function just because you want to store move-only function object involves 1 (or 2 in case of libc++) extra allocation, 1 extra indirection, thread-safe check if shared state is empty before the call, thread-safe operation of storing a value in the shared state.

I know std::packaged_task has more machinery behind it than std::function. If you want to see my level of knowledge on the subject (which is to say "above average but still largely superficial" ;)) I've got a CppCon 2015 talk where I present an extremely naive std::packaged_task. I don't know much about efficiency, but I'm familiar with its functionality.

Your proposal involves non-trivial library changes. Maybe I should have been explicit that my "counterproposal" of adding implicit conversions also involves non-trivial library changes. I'm not really arguing about the behind-the-scenes implementation of the entity; I'm just suggesting that the exposed interface for the entity ought to be spelled std::packaged_task<void(A...)> instead of std::packaged_task<ignore_t(A...)>.

In part 2 of your paper, your example code uses mt_queue<std::function<void()>> instead of mt_queue<unique_function<void()>>, which might cause you to incur extra copies. You should know that I consider that issue "solved", because I believe that std::unique_function will be introduced in C++20 and/or every relevant codebase will have their own my::unique_function by then.

The way I would write your part-2 code is

using task_queue = mt_queue<std::packaged_task<void()>>;

template<typename F>
auto post_function(task_queue& queue, F&& func)
{
    using R = std::result_of_t<F()>;
    auto task = std::packaged_task<R()>(func);
    auto res = task->get_future();
    queue.push(std::packaged_task<void()>(std::move(task)));  // proposes an efficient conversion from packaged_task<R(A...)> to packaged_task<void(A...)>
    return res;
}

or even

using task_queue = mt_queue<std::unique_function<void()>>;  // proposes N4543's std::unique_function

template<typename F>
auto post_function(task_queue& queue, F&& func)
{
    using R = std::result_of_t<F()>;
    auto task = std::packaged_task<R()>(func);
    auto res = task->get_future();
    queue.push(std::packaged_task<void()>(std::move(task)).get_function());  // proposes an efficient member function packaged_task::get_function
    return res;
}

The relevant thing here, the reason I'm bikeshedding so hard, is that I consider it valuable to continue using the keyword "void" to mean "this function returns nothing of interest". I don't like that your proposal makes up a new way to spell "this function returns nothing of interest" — you're spelling it ignore_t() instead of void().

The main motivation of this proposal is to provide building block to implement executors (especially when this concept will be published with Concurrency TS 2). For example Implementation of TwoWayExecutor (p0443r1 terminology) which uses std::future for communication channel requires to pack together shared state and function to be executed, take a future from it and erase the result type of the packaged task in order to store it in the execution context till the time it will be executed. My proposal is to provide zero cost way to erase result type of the packaged_task. There are no ways to write such type erasure in the user code right now (any user code implementation will not be zero cost).

I agree that zero-cost-ness is important, and I repeat that I have no idea whether my "counterproposals" can be made zero-cost (but I want to see somebody explain why they aren't and can't be).
I agree that this problem cannot be solved by using user code to bolt anything onto std::packaged_task; it requires non-trivial standard library changes. Right now, since std:: provides no one-stop-shop solution, the only efficient solution is for everyone to implement their own everything — my::unique_function and my::future compose to provide my::packaged_task, and so on.
My impression is that this is the same situation we were in with std::future prior to the introduction of std::future::then. Back then, anybody who wanted ::then had to implement my::future from scratch in order to get it, because you could not use user code to bolt ::then onto std::future in a zero-cost way.

Hope this clarifies my position,
–Arthur

Sergey Vidyuk

unread,
Feb 22, 2017, 11:29:43 AM2/22/17
to ISO C++ Standard - Future Proposals, sir.v...@gmail.com
среда, 22 февраля 2017 г., 7:26:35 UTC+7 пользователь Bryce Glover написал:
 
I've seen the issue you are talking about in the google drive pdf viewer. There are no such formatting issues when I open the document with ocular on my local computer. I'll play with the styles to fix this issue a little bit later.

Sergey

Sergey Vidyuk

unread,
Feb 22, 2017, 2:26:54 PM2/22/17
to ISO C++ Standard - Future Proposals, sir.v...@gmail.com


среда, 22 февраля 2017 г., 8:19:08 UTC+7 пользователь Arthur O'Dwyer написал:
On Mon, Feb 20, 2017 at 6:22 PM, Sergey Vidyuk <sir.v...@gmail.com> wrote:
вторник, 21 февраля 2017 г., 7:03:42 UTC+7 пользователь Arthur O'Dwyer написал:
On Sunday, February 19, 2017 at 8:49:41 PM UTC-8, Sergey Vidyuk wrote:
Sometimes it's required to store std::packaged_task instances with the same argumet types but different result type in the same collection (especially when implementing your own executor-like class). I've started to work on a proposal to provide partial specialization for the std::packaged_task class template which erases result type for the task. The text is here: https://github.com/VestniK/portable_concurrency/blob/result-erased-task-proposal/proposal/proposal.tex pdf version is in attachement. [...]

I don't understand why we couldn't solve this problem more simply just by making std::function<void(A...)> constructible from std::function<R(A...)>&&, std::future<void> constructible from std::future<T>&&, std::packaged_task<void(A...)> constructible from std::packaged_task<R(A...)>&&, and so on [...]
My intuition is that

    std::packaged_task<int()> pti( [](){ return 42; } );
    std::future<int> fi = pti.get_future();
    std::packaged_task<void()> ptv = std::move(pti);

ought to be legal, IMHO, because its meaning is "obvious"; it's equivalent to

    std::packaged_task<void()> ptv( [pti_ = std::move(pti)](){ pti_(); } );

Even

    std::packaged_task<int()> pti( [](){ return 42; } );
    std::packaged_task<void()> ptv = std::move(pti);
    std::future<void> fv = ptv.get_future();

has an "obvious" meaning IMHO, and I'd like to see it supported if it helps use-cases like Sergey's.

packaged_task is not just type-erasure for function. It's write end of the single-value thread-safe communication channel with capabilities to notify read channel about value delivery combined together with type-erasure for function with additional protection from calling wrapped function twice. Using it like std::function just because you want to store move-only function object involves 1 (or 2 in case of libc++) extra allocation, 1 extra indirection, thread-safe check if shared state is empty before the call, thread-safe operation of storing a value in the shared state.

I know std::packaged_task has more machinery behind it than std::function. If you want to see my level of knowledge on the subject (which is to say "above average but still largely superficial" ;)) I've got a CppCon 2015 talk where I present an extremely naive std::packaged_task. I don't know much about efficiency, but I'm familiar with its functionality.

Your proposal involves non-trivial library changes. Maybe I should have been explicit that my "counterproposal" of adding implicit conversions also involves non-trivial library changes. I'm not really arguing about the behind-the-scenes implementation of the entity; I'm just suggesting that the exposed interface for the entity ought to be spelled std::packaged_task<void(A...)> instead of std::packaged_task<ignore_t(A...)>.

Personally I like the idea to spell result erased task as void(A...) task instead of ignore_t(A...) but isn't it breaking change? C++11 requires that the following code will not throw future_already_retrieved error:

std::packaged_task<std::string(int)> task1([](int x) {return std::to_string(x);});
auto f1 = task1.get_future();
std::packaged_task<void(int)> task2(task1);
auto f2 = task2.get_future();

If std::packaged_task<void(int)> becomes result erasure for packaged tasks with the same argument types then the last line must throw this error. My personal opinion here is that std::future API in it's current state is half baked and totally unusable so "no backward compatibility breaks" rule should not be so strict here but it's just an opinion.


In part 2 of your paper, your example code uses mt_queue<std::function<void()>> instead of mt_queue<unique_function<void()>>, which might cause you to incur extra copies. You should know that I consider that issue "solved", because I believe that std::unique_function will be introduced in C++20 and/or every relevant codebase will have their own my::unique_function by then.


unique_function will eliminate one unnecessary allocation: ugly make_shared<packaged_task<R(A...)>> but there is still one allocation
to perform type-erasure unless there is strict requirement on this class to have embedded storage with enough space to store a packaged_task. Such requirement might be reasonable. unique_function proposes "no double wrapping" optimization for std::function. Adding same requirement for packaged_task makes unique_function ultimate function type erasure which provides best efficiency for any standard function (raw function pointer) and function wrappers (both existing function type erasures in the standard library). With such requirements there is no need in my proposal at all.
 
The way I would write your part-2 code is

using task_queue = mt_queue<std::packaged_task<void()>>;

template<typename F>
auto post_function(task_queue& queue, F&& func)
{
    using R = std::result_of_t<F()>;
    auto task = std::packaged_task<R()>(func);
    auto res = task->get_future();
    queue.push(std::packaged_task<void()>(std::move(task)));  // proposes an efficient conversion from packaged_task<R(A...)> to packaged_task<void(A...)>
    return res;
}

or even

using task_queue = mt_queue<std::unique_function<void()>>;  // proposes N4543's std::unique_function

template<typename F>
auto post_function(task_queue& queue, F&& func)
{
    using R = std::result_of_t<F()>;
    auto task = std::packaged_task<R()>(func);
    auto res = task->get_future();
    queue.push(std::packaged_task<void()>(std::move(task)).get_function());  // proposes an efficient member function packaged_task::get_function
    return res;
}

The relevant thing here, the reason I'm bikeshedding so hard, is that I consider it valuable to continue using the keyword "void" to mean "this function returns nothing of interest". I don't like that your proposal makes up a new way to spell "this function returns nothing of interest" — you're spelling it ignore_t() instead of void().

The main motivation of this proposal is to provide building block to implement executors (especially when this concept will be published with Concurrency TS 2). For example Implementation of TwoWayExecutor (p0443r1 terminology) which uses std::future for communication channel requires to pack together shared state and function to be executed, take a future from it and erase the result type of the packaged task in order to store it in the execution context till the time it will be executed. My proposal is to provide zero cost way to erase result type of the packaged_task. There are no ways to write such type erasure in the user code right now (any user code implementation will not be zero cost).

I agree that zero-cost-ness is important, and I repeat that I have no idea whether my "counterproposals" can be made zero-cost (but I want to see somebody explain why they aren't and can't be).
I agree that this problem cannot be solved by using user code to bolt anything onto std::packaged_task; it requires non-trivial standard library changes. Right now, since std:: provides no one-stop-shop solution, the only efficient solution is for everyone to implement their own everything — my::unique_function and my::future compose to provide my::packaged_task, and so on.
My impression is that this is the same situation we were in with std::future prior to the introduction of std::future::then. Back then, anybody who wanted ::then had to implement my::future from scratch in order to get it, because you could not use user code to bolt ::then onto std::future in a zero-cost way.


I agree with you here. Unfortunately everybody have their own futures due the half-baked state of the std::future API without any ways to extend it from the user code and now it looks like Concurrency TS2 is going to provide Future concept to cover them all (p0443r1 as far as I can google is the most up to date state of work on the Executors API for the standard library). We might end up with necessity to work with several libraries with async API on top of different incompatible future types in a single project.

Hope this clarifies my position,
–Arthur

Thank you for an explanation I really misunderstood your first letter.

Sergey

Arthur O'Dwyer

unread,
Feb 22, 2017, 3:43:25 PM2/22/17
to ISO C++ Standard - Future Proposals, Sergey Vidyuk
On Wed, Feb 22, 2017 at 11:26 AM, Sergey Vidyuk <sir.v...@gmail.com> wrote:

Personally I like the idea to spell result erased task as void(A...) task instead of ignore_t(A...) but isn't it breaking change? C++11 requires that the following code will not throw future_already_retrieved error:

std::packaged_task<std::string(int)> task1([](int x) {return std::to_string(x);});
auto f1 = task1.get_future();
std::packaged_task<void(int)> task2(std::move(task1));
auto f2 = task2.get_future();

(I've added the missing "std::move" to the code above.)
Darn it, you're right. The way things stand today,

    std::packaged_task<void(int)> task2(std::move(task1));

creates a new packaged_task where calling get_future() is legal; but

    std::packaged_task<std::string(int)> task2(std::move(task1));

steals the state of the right-hand side and creates an object where calling get_future() is not legal.
This seems completely unintuitive to me and I wish it were changed; but I have no idea whether that's feasible at this point.

unique_function will eliminate one unnecessary allocation: ugly make_shared<packaged_task<R(A...)>> but there is still one allocation
to perform type-erasure unless there is strict requirement on this class to have embedded storage with enough space to store a packaged_task. Such requirement might be reasonable. unique_function proposes "no double wrapping" optimization for std::function. Adding same requirement for packaged_task makes unique_function ultimate function type erasure which provides best efficiency for any standard function (raw function pointer) and function wrappers (both existing function type erasures in the standard library). With such requirements there is no need in my proposal at all.

I do like "no double wrapping" optimizations.
The way I implemented packaged_task<R()> in my talk was basically as a pair<unique_function<void()>, future<R>>. Unfortunately mathematics prevents us from requiring that

    sizeof (unique_function<void()>)
      >=
    sizeof (packaged_task<R()>)
      ==
    sizeof (pair<unique_function<void()>, future<R>>)
      >
    sizeof (unique_function<void()>)

But I guess my::packaged_task<R()> could be implemented as a unique_ptr<pair<...>> without loss of efficiency. In fact, storing the functor (the "task") in the shared state would increase efficiency by removing the requirement to move-construct the "task" every time the packaged_task itself is move-constructed. Right now, N4618 30.6.9.1 [futures.task.members]/6 and /10 are very clear that the "stored task" must be move-constructed, swapped, etc., every time those operations are performed on the std::packaged_task; if you want cheaply movable or swappable packaged_tasks you have to implement your own my::packaged_task.  (In particular, if you want noexcept-swappable packaged_tasks, I think. N4618's swap is declared noexcept, but you can easily write a swap for your stored task type that throws an exception, which means that swapping packaged_tasks containing those tasks ought to result in a call to std::terminate.)

Unless I'm messing something up, I observe that both libc++ and libstdc++ utterly ignore [futures.task.members]/6; both of them provide cheaply, noexceptly swappable packaged_task.  Good for them!  But maybe there should be a paper that "fixes" the standard to match the implementations?

–Arthur

Bryce Glover

unread,
Feb 22, 2017, 5:11:16 PM2/22/17
to Sergey Vidyuk, std-pr...@isocpp.org
On Feb 22, 2017, at 3:04 PM, std-pr...@isocpp.org wrote:

Sergey Vidyuk <sir.v...@gmail.com>: Feb 22 08:29AM -0800 


среда, 22 февраля 2017 г., 7:26:35 UTC+7 пользователь Bryce Glover написал:
 
(snipped…)
 
I’ve seen the issue you are talking about in the google drive pdf viewer. There are no such formatting issues when I open the document with ocular on my local computer. I'll play with the styles to fix this issue a little bit later.
 
Sergey

Huh, that is weird.  Hopefully you can figure it out, then…

— Bryce Glover

Sergey Vidyuk

unread,
Apr 6, 2017, 12:59:42 PM4/6/17
to ISO C++ Standard - Future Proposals, sir.v...@gmail.com
Sorry for disappearing for a long time. I had not enough time to return to this topic due to some family issues.

четверг, 23 февраля 2017 г., 3:43:25 UTC+7 пользователь Arthur O'Dwyer написал:
But I guess my::packaged_task<R()> could be implemented as a unique_ptr<pair<...>> without loss of efficiency. In fact, storing the functor (the "task") in the shared state would increase efficiency by removing the requirement to move-construct the "task" every time the packaged_task itself is move-constructed.

libstdc++ stores task inside the shared state and sizeof(packaged_task) == sizeof(promise) == sizeof(shared_ptr)
 
Right now, N4618 30.6.9.1 [futures.task.members]/6 and /10 are very clear that the "stored task" must be move-constructed, swapped, etc., every time those operations are performed on the std::packaged_task; if you want cheaply movable or swappable packaged_tasks you have to implement your own my::packaged_task.  (In particular, if you want noexcept-swappable packaged_tasks, I think. N4618's swap is declared noexcept, but you can easily write a swap for your stored task type that throws an exception, which means that swapping packaged_tasks containing those tasks ought to result in a call to std::terminate.)


As far as I can understand packaged_task::swap specification 30.6.9.1 [futures.task.members]/10 there is no direct requirement to call swap on stored tasks:
 Effects: exchanges the shared states and stored tasks of *this and other.

Postcondition: *this has the same shared state and stored task (if any) as other prior to the call to swap. other has the same shared state and stored task (if any) as *this prior to the call to swap.

and since two different packaged_task instances could store function objects of completely different types packaged_task::swap almost always will fail to find swap specialization for exact types of stored tasks.

I've asked about move constructor requirements on the standard discussion forum: https://groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/FqyKCoy4W8I

unique_function seems to be generic solution for a problem which my proposal tries to solve. What is the status of N4543 is it gouing to be merged into C++20 draft when the work on the next version of the standard begins?

Sergey Vidyuk
Reply all
Reply to author
Forward
0 new messages