More about resumable functions in C++

With the release of Visual Studio 2015 RC, we wanted to provide an update on the progress of resumable functions support in Visual C++. Since last time, we have made some changes to our experimental implementation that tracks the latest proposal (with the exception that resumable_traits and resumable_handle are called coroutine_traits and coroutine_handle as in earlier proposal). Please note that this is still an experimental feature that is currently available only for x64 targets and also requires an opt-in switch (/await) to use it.

What’s New?

Auto-type deduction

We enable auto type deduction for resumable functions

#include<iostream>

#include<experimental/generator>

auto hello() {

       for (auto ch : “Hello, worldn”)

              yield ch;

}

int main() {

       for (auto ch : hello())

              std::cout << ch;

}

The rules for type deduction are as follows:

If a function return type is auto or declspec(auto) and no trailing return type is specified, then the return type of the resumable function is deduced as follows:

  • If a yield statement and either an await expression or an await-for statement are present, then the return type is std::experimental::async_stream<T >, where T is deduced from the yield statements
  • Otherwise, if an await expression or an await-for statement are present in a function, then the return type is std::experimental::task<T> where type T is deduced from return statements
  • Otherwise, if a yield statement is present in a function, then the return type is std::experimental::generator<T>

In Visual Studio 2015 RC, we only provide an implementation for std::experimental::generator<T>.

Experimental keywords

Experimental keywords “await” and “yield” are available with /await switch.

yield is a context sensitive keyword and will be interpreted as an identifier if followed by ‘(‘. This rule may change in the future.

Optional member functions

  • set_exception member function is now optional

    If coroutine_promise does not have this function, exceptions will propagate out of resumable functions normally. (Note that generators that are executed synchronously with the caller do not need to provide set_exception)

  • set_result member function is now optional

    If not present, await cannot be used in the body of the resumable function. (Generators do not define this member function in its promise and thus a mistake of using await in a generator will be caught at compile time)

    There were simplifications to requirements of initial_suspend, final_suspend and yield_value member functions. They are no longer required to return awaitable type. Now they return true (if suspend is required) or false. The yield_value member function can also have a void return type, which is interpreted as being followed by unconditional suspend.

    The following is a motivating example when yield_value may need to return a boolean value to control whether suspension is needed or not.

    recursive_generator<int> walk(node* root) {

         if (root) {

            yield walk(root->left); // calls an overload that takes a generator

            yield root->value; // calls an overload that takes int

            yield walk(root->right); // calls an overload that takes a generator

         }

    }

    The example above is using a recursive_generator (not in the proposal, but can be implemented using the resumable functions). In this case, recursive invocations of yield walk(…) may not yield any values (if tree is empty), in that case, yield_value must return false. Thus an overload of yield_value that takes a recursive_generator as an argument, must return bool. An overload of yield_value that takes an int can be of void type as it always returns a value.

Changes to cancellation mechanism

Instead of using cancellation_requested() member function in a promise to indicate that on the next resume resumable function need to be cancelled, an explicit member function destroy() is added to the coroutine_handle. A destroy() member function can be invoked to force resumption of coroutine to go on the cancel path.

This change mostly affects library writer as it simplifies writing generators and tasks types.

Known bugs / limitations:

  1. Cannot use Windows Runtime (WinRT) types in the signature of resumable function and resumable function cannot be a member function in a WinRT class. (This is fixed, but didn’t make it in time for RC release)
  2. We may give a wrong diagnostic if return statement appears in resumable function prior to seeing an await expression or yield statement. (Workaround: restructure your code so that the first return happens after yield or await)
  3. Compiling code with resumable functions may result in compilation errors or bad codegen if compiled with /ZI flag (Edit and Continue debugging)
  4. Parameters of a resumable function may not be visible while debugging

Sorry for the repeat disclaimer. We know there are bugs and we continue to work on them. This is still an experimental feature and the purpose is to get design feedback from you. We look forward to hearing from you.

Gor Nishanov and Raman Sharma