Open e48d0f12-b0d3-485a-93ce-75b823b0ccac opened 8 years ago
OK, so according to the standard this is not a bug in libc++. The semantics of async act according to the spec. However I would agree that it is a bug in the spec that allows the move to happen twice.
The provided reproducer depends on the order of evaluation for function arguments when evaluating the expression INVOKE(fn, decay_copy(string&&), decay_copy(S&&)). Clang evaluates decay_copy(string&&)
first and then decay_copy(S&&)
meaning the string gets moved from before the exception is thrown. GCC evaluates them in reverse order, meaning the string is never moved from because S&& is always evaluated first.
This is why libstdc++ w/ GCC appears to pass your reproducer while libc++ w/ clang appears to fail. In reality both libc++ and libstdc++ pass when using GCC and fail when using Clang.
On the same note if you switch the order of '[](std::string&& s, S&&)' to be '[](S&&, std::string&&)' the reproducer will fail with GCC and pass with Clang.
I'll look into filing a standard defect against the wording for async, allowing a pure copy to be done, but I wouldn't expect a fix to this any time soon.
Extended Description
In the libc++'s current implementation of std::async with default policy (i.e. async | deferred), first, the argument function is launched in the context of a new thread asynchronously (async). If it was failed, the function is launched in the context of the current thread later (deffered).
But if the first launch was failed (ex. std::thread creation trows an exception), std::async's arguments was already moved. So, the arguments have no valid values when second launch is executing.
The sample code below simulates std::thread creation failure.
============================== sample code ==============================
include
include
include
include
struct S { S() : b(false) {} // to simulate thread creation failure, it throws resource_unavailable_try_again when 'o' is first move target S(S&& o) : b(true) { if (!o.b) { o.b = true; throw std::system_error(std::make_error_code(std::errc::resource_unavailable_try_again)); } } S(const S&) = delete; S& operator=(const S&) = delete; S&& operator=(S&&) = delete; bool b; };
int main() { auto f = std::async([](std::string&& s, S&&){ std::cout << "s = '" << s << '\'' << std::endl; }, std::string{"str"}, S{}); f.get(); } ============================== sample code ============================== ============================== output ============================== s = '' ============================== output ============================== cf. http://melpon.org/wandbox/permlink/IZDRW8D7i0wifN91
I think that it should output "s = 'str'" or should output nothing, whether exception is thrown or not.