2

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?

Fedor
  • 17,146
  • 13
  • 40
  • 131

1 Answers1

1

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.

user17732522
  • 53,019
  • 2
  • 56
  • 105