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?
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.