c++language-lawyercopy-constructorexplicitstdasync

Explicit copy constructor of a parameter passed via std::async


In the following program foo function is executed asynchronously and its argument of type A is copied inside async by value:

#include <future>

struct A {
    A() = default;
    explicit A(const A &) = default;
};

void foo(const A &) {}

int main() {
    auto f = std::async(std::launch::async, foo, A{});
}

Despite copy-constructor of A is explicit, the program is accepted in the latest MSVC and in libstdc++ from GCC 10. But starting from GCC 11 the program is rejected with the

error: could not convert '{std::forward<void (&)(const A&)>((* & __args#0)), std::forward<A>((* & __args#1))}' from '<brace-enclosed initializer list>' to 'std::tuple<void (*)(const A&), A>'
 1693 |           _M_fn{{std::forward<_Args>(__args)...}}

Online demo: https://gcc.godbolt.org/z/r3PsoaxT7

Is GCC 11 more correct here or it is a regression?


Solution

  • In the post-C++20 draft (https://timsong-cpp.github.io/cppwp/n4868/futures.async) there is a "Mandates:" clause which effectively only requires std::is_move_constructible_v<A> to be satisfied. This tests whether a declaration of the form

    A a(std::declval<A&&>());
    

    would be well-formed, which it is even with explicit on the constructor. Therefore the "Mandates:"-clause is satisfied. (If it was not, the program would be ill-formed.)

    However there is also a "Precondition:" clause effectively requiring A to be Cpp17MoveConstructible. This library concept has the effective requirement that

    A a = rv;
    

    where rv is a rvalue of type A (see https://timsong-cpp.github.io/cppwp/n4868/library#tab:cpp17.moveconstructible), is well-formed. This is not the case here, since this is copy-initialization which doesn't consider explicit constructors. Theferore the "Precondition:" clause is not satisfied, which causes the program to have undefined behavior.

    As a result both behaviors are conforming.


    However, in the current draft the precondition has been removed with resolution of LWG issue 3476 (draft commit) and I don't see anything else that would forbid the type from being usable as an argument to std::async. The arguments are now specified to be constructed with auto(/*...*/) which does consider explicit constructors. The resolution of the mentioned issue explicitly says that no other copy/move should be allowed either.