0

I'm trying to return a custom type from a Concurrency Runtime task. My custom type should be constructible through a static factory method only (aside from being move-constructible), e.g.:

#include <utility>

struct foo
{
    foo() = delete;
    foo(foo const&) noexcept = delete;
    foo& operator=(foo const&) noexcept = delete;

    foo(foo&&) noexcept = default;
    foo& operator=(foo&&) noexcept = default;

    static foo make_foo(int const value) noexcept { return std::move(foo{ value }); }

private:
    foo(int const) noexcept {}
};

And a simple test case:

#include <ppltasks.h>
using namespace concurrency;

int main()
{
    auto&& task
    {
        create_task([value = 42]()
        {
            return foo::make_foo(value);
        })
    };
    auto&& result{ task.get() };
}

This, however, fails to compile, producing the following (abridged) compiler diagnostic:

ppltasks.h(644,1): error C2280:  'foo::foo(const foo &) noexcept': attempting to reference a deleted function
main.cpp(223): message :  see declaration of 'foo::foo'
main.cpp(223,5): message :  'foo::foo(const foo &) noexcept': function was explicitly deleted

No surprises here, the copy-c'tor is indeed explicitly deleted, on purpose. I just don't understand, why the move-constructor isn't considered as a candidate.

It seems that I can get the code to compile, as long as I provide a default-c'tor, a copy-c'tor, and a copy-assignment operator. Is this a limitation of the library (ConcRT), the compiler (Visual Studio 2019 16.1.0), the programming language, or my control over it?

Phrased another way: Is there anything I can do to get my custom type (as it is) to play along with concurrency::task, or are default-constructibility, copy-constructibility, and copy-assignability undocumented requirements?

IInspectable
  • 46,945
  • 8
  • 85
  • 181
  • 1
    A task can be `then`'d more than once: `auto t = create_task(…); t.then([](foo f) {…}); t.then([](foo f) {…});`. If the object is move-only, then the second `then` wouldn't be able to receive the result because the first one took it. To make `then` work properly, the object must be copyable. One workaround is to wrap it in a `shared_ptr`. Another workaround is to create a wrapper class whose copy constructor performs a move (so the second `then` receives an empty `foo`). – Raymond Chen Apr 21 '19 at 15:36
  • @ray: Took me a whole night to think this trough, and I'm sure you are right if I were using continuations. But I'm not, and I can't see a reason, why the framework wouldn't be able to *move* the returned object into its private storage area. I'd be fine to accept, that objects are required to be copyable in case continuations are used. This also doesn't explain the need for a default c'tor. Regardless, although neither workaround is entirely enticing, I'll go with a pointer indirection as you suggested. Thanks! – IInspectable Apr 22 '19 at 06:17
  • 2
    Even without continuations, you could call `.get()` twice. I don't see any error message that requires a default constructor. – Raymond Chen Apr 22 '19 at 19:31
  • @ray: That's true, even if rare. I hadn't considered that scenario. The error message in the question doesn't ask for a default c'tor. Once you supply a copy c'tor and a copy assignment operator, the compiler will complain about the missing default c'tor, trying to instantiate some internal class template (`_ResultHolder`). – IInspectable Apr 22 '19 at 20:41
  • Ah, there it is. They even have the comment `// this means that the result type must have a public default ctor.` so I guess it was an acknowledged limitation. You can wrap your object inside a `std::optional` I guess. – Raymond Chen Apr 22 '19 at 22:13

0 Answers0