chriskohlhoff / asio

Asio C++ Library
http://think-async.com/Asio
4.88k stars 1.21k forks source link

Completion Signature Ref-Qualification Breaks Under C++20 #1395

Open RobertLeahy opened 9 months ago

RobertLeahy commented 9 months ago
#include <boost/asio.hpp>
#include <system_error>

using completion_signature = void(std::error_code);
using progress_signature = void(int) &;

struct completion_handler {
    void operator()(std::error_code&&) &&;
    void operator()(int&&) &;
};

template<typename CompletionToken>
decltype(auto) async_operation(CompletionToken&& token) {
    return boost::asio::async_initiate<
        CompletionToken,
        completion_signature,
        progress_signature>(
            [](auto h) { },
            token);
}

int main() {
    async_operation(completion_handler{});
}

Builds in C++17 but not C++20. Seems ref-qualification is stripped away at some point in the process of trying to call async_result::initiate and therefore once concept checking kicks in the build fails since the std::is_invocable_v<completion_handler, int> is false (which should be fine since the matching completion signature is lvalue-qualified and therefore std::is_invocable_v<completion_handler&, int> is what should be checked).

Compiler explorer reproduction.

diehard2 commented 9 months ago

@RobertLeahy I'm having the same issue. Did you find a workaround?

RobertLeahy commented 9 months ago

Declaring (but not defining) the rvalue-qualified operator() allows the code to compile since it satisfies the compile time check. Since the rvalue-qualified version will never be invoked (assuming the asynchronous operation is correctly implemented) this effectively works around the issue.

Modifying the example from the issue so it builds in C++20:

#include <boost/asio.hpp>
#include <system_error>

using completion_signature = void(std::error_code);
using progress_signature = void(int) &;

struct completion_handler {
    void operator()(std::error_code&&) && {
        //  TODO: Actual implementation here
    }
    void operator()(int&&) & {
        //  TODO: Actual implementation here
    }
    //  Never evaluated, presence just allows code
    //  to compile
    void operator()(int&&) &&;
};

template<typename CompletionToken>
decltype(auto) async_operation(CompletionToken&& token) {
    return boost::asio::async_initiate<
        CompletionToken,
        completion_signature,
        progress_signature>(
            [](auto h) { },
            token);
}

int main() {
    async_operation(completion_handler{});
}

Compiler explorer link here.