0

I have been trying to implement a cooperative job scheduler using C++ co-routines. This involves putting co-routines into an array. However I get the error Object of type 'Job' cannot be assigned because its copy assignment operator is implicitly deleted when I try to do this. I do not completely understand the bellow code, but I am under the impression I can't just make Job moveable/copyable.

Job.h

#include <experimental/coroutine>
#include <cstddef>

class Job {
public:
    struct promise_type;
    using coro_handle = std::experimental::coroutine_handle<promise_type>;
    Job() { handle_ = NULL; } // default value to create arrays
    Job(coro_handle handle) : handle_(handle) { assert(handle); }
    Job(Job&) = delete;
    Job(Job&&) = delete;
    bool resume() {
        if (not handle_.done())
            handle_.resume();
        return handle_.done();
    }
    ~Job() { handle_.destroy(); }
private:
    coro_handle handle_;
};

struct Job::promise_type {
    using coro_handle = std::experimental::coroutine_handle<promise_type>;
    auto get_return_object() {
        return coro_handle::from_promise(*this);
    }
    auto initial_suspend() { return std::experimental::suspend_always(); }
    auto final_suspend() { return std::experimental::suspend_always(); }
    void return_void() {}
    void unhandled_exception() {
        std::terminate();
    }
};

Main.cpp

#include "Job.h"
Job testCoro() {
    co_return;
} // Empty coro

void main() {
    Job jobList[2048];
    jobList[0] = testCoro(); // Call coro and insert it into the list of jobs, error on this line
}

Source: https://blog.panicsoftware.com/your-first-coroutine/

I have tried the following:

  • Passing around a pointer to the Job, but it runs into object lifetime issues, since if it is created on the stack in the function and the function exits, a dangling pointer is left behind
  • Ignoring the non-move-ability and forcing it, causes spurious errors and invokes UB
  • C++ container with non-copyable non-movable element type, but I can't create a placement new operator, due to co-routines semantics

EDIT 1: Added co_return

  • "*I am under the impression I can't just make Job moveable/copyable.*" Why not? How else could any of this possibly work? I mean, if you default initialize one, how would it ever become a viable `Job` unless you can copy or move into it? – Nicol Bolas Jan 01 '20 at 22:40
  • Also, it's not really clear to me what the point of a job scheduler is for a coroutine system that's built on each coroutine scheduling its resumption with the asynchronous process it's waiting on. Your design seems to assume the coroutine is scheduling its resumption with the caller, which is not generally how it works. – Nicol Bolas Jan 01 '20 at 22:41
  • @NicolBolas The default constructor was only created so that I could define an array which contained them, otherwise there would be an error on the line `Job jobList[2048];`. I do not know how I would make it is a viable job which is why I am asking. – Evan La Fontaine Jan 01 '20 at 23:04
  • The jobsystem is kind of meat to act like multi threading, with long running co-routines which periodical yield to let others run (ie. one is doing path finding calculations whilst another is handling the UI). This is to avoid using a state-machine. – Evan La Fontaine Jan 01 '20 at 23:04

1 Answers1

1

How to get a non-moveable and non-copyable function return value into an array

Job jobList[2048];
obList[0] = testCoro();

There is no way to assign non-movable types. Only way to get such object into an array is by initialising the array element directly. For example:

Job jobList[] {testCoro()};

If this is not an option, then you cannot use an array of your non-movable objects.

An alternative data structure is a custom container with constantly sized array of char storage reused with placement new, as in the linked question. It's unclear why the "co-routine semantics" would prevent that.

Community
  • 1
  • 1
eerorika
  • 232,697
  • 12
  • 197
  • 326
  • "co-routine semantics" was referring to how a co-routine function must return a struct which can be constructed with a coroutine_handle. Hence I cannot use placement new as it is a function return value, is there a way to do placement return (since I am getting a returned object not creating a new one). – Evan La Fontaine Jan 01 '20 at 23:10
  • @EvanLaFontaine: "*a co-routine function must return a struct which can be constructed with a coroutine_handle.*" Yes, but `coroutine_handle` is copyable. – Nicol Bolas Jan 01 '20 at 23:18
  • It is! for some reason I assumed that there was something magic about the handle's location (as though it was like a mutex or something). If that is the case then I can just undelete the constructors on Job. – Evan La Fontaine Jan 02 '20 at 05:10