diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -140,6 +140,7 @@ __coroutine/noop_coroutine_handle.h __coroutine/trivial_awaitables.h __debug + __detail/transaction.h __errc __format/format_arg.h __format/format_args.h diff --git a/libcxx/include/__detail/transaction.h b/libcxx/include/__detail/transaction.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__detail/transaction.h @@ -0,0 +1,89 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP___DETAIL_TRANSACTION_H +#define _LIBCPP___DETAIL_TRANSACTION_H + +#include <__config> +#include <__utility/exchange.h> +#include <__utility/move.h> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +#pragma GCC system_header +#endif + +#if _LIBCPP_STD_VER > 17 + +_LIBCPP_BEGIN_NAMESPACE_STD + +// __transaction is a helper class for writing code with the strong exception guarantee. +// +// When writing code that can throw an exception, one can store rollback instructions in a +// transaction so that if an exception is thrown at any point during the lifetime of the +// transaction, it will be rolled back automatically. When the transaction is done, one +// must mark it as being complete so it isn't rolled back when the transaction is destroyed. +// +// Transactions can't be copied or assigned to, but they can be moved around for convenience. +// +// __transaction can help greatly simplify code that would normally be cluttered by +// `#if _LIBCPP_NO_EXCEPTIONS`. For example: +// +// template +// Iterator uninitialized_copy_n(Iterator iter, Size n, OutputIterator out) { +// typedef typename iterator_traits::value_type value_type; +// __transaction transaction([start=out, &out] { +// std::destroy(start, out); +// }); +// +// for (; n > 0; ++iter, ++out, --n) { +// ::new ((void*)std::addressof(*out)) value_type(*iter); +// } +// transaction.__complete(); +// return out; +// } +// +template +struct __transaction { + __transaction() = default; + + _LIBCPP_HIDE_FROM_ABI + constexpr explicit __transaction(_Rollback __rollback) + : __rollback_(_VSTD::move(__rollback)) + , __completed_(false) + { } + + constexpr __transaction(__transaction&& __other) + : __rollback_(_VSTD::move(__other.__rollback_)) + , __completed_(_VSTD::exchange(__other.__completed_, true)) + { } + + __transaction(__transaction const&) = delete; + __transaction& operator=(__transaction const&) = delete; + __transaction& operator=(__transaction&&) = delete; + + _LIBCPP_HIDE_FROM_ABI + constexpr void __complete() { + __completed_ = true; + } + + _LIBCPP_HIDE_FROM_ABI + _LIBCPP_CONSTEXPR_AFTER_CXX17 ~__transaction() { + if (!__completed_) + __rollback_(); + } + +private: + [[no_unique_address]] _Rollback __rollback_; + bool __completed_; +}; + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_STD_VER > 17 + +#endif // _LIBCPP___DETAIL_TRANSACTION_H diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap --- a/libcxx/include/module.modulemap +++ b/libcxx/include/module.modulemap @@ -439,6 +439,11 @@ export initializer_list export * } + + module __detail { + private header "__detail/transaction.h" + } + module exception { header "exception" export * diff --git a/libcxx/test/libcxx/detail/transaction.pass.cpp b/libcxx/test/libcxx/detail/transaction.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/detail/transaction.pass.cpp @@ -0,0 +1,125 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 + +#include <__detail/transaction.h> +#include +#include +#include + +#include "test_macros.h" + +constexpr bool test() { + // Make sure the transaction is rolled back if it is not marked as complete when + // it goes out of scope. + { + bool rolled_back = false; + { + std::__transaction t([&] { rolled_back = true; }); + } + assert(rolled_back); + } + + // Make sure the transaction is not rolled back if it is marked as complete when + // it goes out of scope. + { + bool rolled_back = false; + { + std::__transaction t([&] { rolled_back = true; }); + t.__complete(); + } + assert(!rolled_back); + } + + // Make sure that we will perform the right number of rollbacks when a transaction has + // been moved around + { + // When we don't complete it (exactly 1 rollback should happen) + { + int rollbacks = 0; + { + std::__transaction t([&] { ++rollbacks; }); + auto other = std::move(t); + } + assert(rollbacks == 1); + } + + // When we do complete it (no rollbacks should happen) + { + int rollbacks = 0; + { + std::__transaction t([&] { ++rollbacks; }); + auto other = std::move(t); + other.__complete(); + } + assert(rollbacks == 0); + } + } + + // Basic properties of the type + { + struct Rollback { constexpr void operator()() const { } }; + using Transaction = std::__transaction; + + static_assert(std::is_default_constructible_v); + + static_assert(!std::is_copy_constructible_v); + static_assert(std::is_move_constructible_v); + + static_assert(!std::is_copy_assignable_v); + static_assert(!std::is_move_assignable_v); + + // Make sure a transaction is not default constructible if its rollback + // operation is not default constructible. + { + struct NoDefaultRollback { + NoDefaultRollback() = delete; + constexpr void operator()() const { } + }; + using NoDefaultTransaction = std::__transaction; + static_assert(!std::is_default_constructible_v); + } + } + + return true; +} + +void test_exceptions() { +#ifndef TEST_HAS_NO_EXCEPTIONS + // Make sure the rollback is performed when an exception is thrown during the + // lifetime of the transaction. + { + bool rolled_back = false; + try { + std::__transaction t([&] { rolled_back = true; }); + throw 0; + } catch (...) { } + assert(rolled_back); + } + + // Make sure we don't roll back if an exception is thrown but the transaction + // has been marked as complete when that happens. + { + bool rolled_back = false; + try { + std::__transaction t([&] { rolled_back = true; }); + t.__complete(); + throw 0; + } catch (...) { } + assert(!rolled_back); + } +#endif // TEST_HAS_NO_EXCEPTIONS +} + +int main(int, char**) { + test(); + test_exceptions(); + static_assert(test()); + return 0; +} diff --git a/libcxx/test/libcxx/diagnostics/detail.headers/detail/transaction.module.verify.cpp b/libcxx/test/libcxx/diagnostics/detail.headers/detail/transaction.module.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/diagnostics/detail.headers/detail/transaction.module.verify.cpp @@ -0,0 +1,15 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: modules-build + +// WARNING: This test was generated by 'generate_private_header_tests.py' +// and should not be edited manually. + +// expected-error@*:* {{use of private header from outside its module: '__detail/transaction.h'}} +#include <__detail/transaction.h>