From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTPS id D0BB9385828C for ; Fri, 5 Aug 2022 13:59:30 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org D0BB9385828C Received: from mimecast-mx02.redhat.com (mimecast-mx02.redhat.com [66.187.233.88]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-388-O_OEYI3hM4KRtjqZf5w5qw-1; Fri, 05 Aug 2022 09:59:27 -0400 X-MC-Unique: O_OEYI3hM4KRtjqZf5w5qw-1 Received: from smtp.corp.redhat.com (int-mx05.intmail.prod.int.rdu2.redhat.com [10.11.54.5]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 521C68037B3; Fri, 5 Aug 2022 13:59:27 +0000 (UTC) Received: from localhost (unknown [10.33.36.4]) by smtp.corp.redhat.com (Postfix) with ESMTP id D544B94571; Fri, 5 Aug 2022 13:59:26 +0000 (UTC) From: Jonathan Wakely To: libstdc++@gcc.gnu.org, gcc-patches@gcc.gnu.org Subject: [committed] libstdc++: Implement from LFTSv3 Date: Fri, 5 Aug 2022 14:59:26 +0100 Message-Id: <20220805135926.851044-1-jwakely@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.11.54.5 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Type: text/plain Content-Transfer-Encoding: 8bit X-Spam-Status: No, score=-12.0 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_NUMSUBJECT, KAM_SHORT, RCVD_IN_DNSWL_NONE, SPF_HELO_NONE, SPF_NONE, TXREP, T_SCC_BODY_TEXT_LINE autolearn=unavailable autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: libstdc++@gcc.gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Libstdc++ mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Fri, 05 Aug 2022 13:59:34 -0000 Tested x86_64-linux, pushed to trunk. -- >8 -- libstdc++-v3/ChangeLog: * include/Makefile.am: Add new header. * include/Makefile.in: Regenerate. * include/experimental/scope: New file. * testsuite/experimental/scopeguard/uniqueres.cc: New test. * testsuite/experimental/scopeguard/exit.cc: New test. --- libstdc++-v3/include/Makefile.am | 1 + libstdc++-v3/include/Makefile.in | 1 + libstdc++-v3/include/experimental/scope | 495 ++++++++++++++++++ .../testsuite/experimental/scopeguard/exit.cc | 300 +++++++++++ .../experimental/scopeguard/uniqueres.cc | 360 +++++++++++++ 5 files changed, 1157 insertions(+) create mode 100644 libstdc++-v3/include/experimental/scope create mode 100644 libstdc++-v3/testsuite/experimental/scopeguard/exit.cc create mode 100644 libstdc++-v3/testsuite/experimental/scopeguard/uniqueres.cc diff --git a/libstdc++-v3/include/Makefile.am b/libstdc++-v3/include/Makefile.am index 069a16ec769..3eeb407a57f 100644 --- a/libstdc++-v3/include/Makefile.am +++ b/libstdc++-v3/include/Makefile.am @@ -757,6 +757,7 @@ experimental_headers = \ ${experimental_srcdir}/random \ ${experimental_srcdir}/ratio \ ${experimental_srcdir}/regex \ + ${experimental_srcdir}/scope \ ${experimental_srcdir}/set \ ${experimental_srcdir}/simd \ ${experimental_srcdir}/socket \ diff --git a/libstdc++-v3/include/Makefile.in b/libstdc++-v3/include/Makefile.in index 36eff73139d..e24563caaed 100644 --- a/libstdc++-v3/include/Makefile.in +++ b/libstdc++-v3/include/Makefile.in @@ -1115,6 +1115,7 @@ experimental_headers = \ ${experimental_srcdir}/random \ ${experimental_srcdir}/ratio \ ${experimental_srcdir}/regex \ + ${experimental_srcdir}/scope \ ${experimental_srcdir}/set \ ${experimental_srcdir}/simd \ ${experimental_srcdir}/socket \ diff --git a/libstdc++-v3/include/experimental/scope b/libstdc++-v3/include/experimental/scope new file mode 100644 index 00000000000..37a57b38af7 --- /dev/null +++ b/libstdc++-v3/include/experimental/scope @@ -0,0 +1,495 @@ +// -*- C++ -*- + +// Copyright The GNU Toolchain Authors. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// Under Section 7 of GPL version 3, you are granted additional +// permissions described in the GCC Runtime Library Exception, version +// 3.1, as published by the Free Software Foundation. + +// You should have received a copy of the GNU General Public License and +// a copy of the GCC Runtime Library Exception along with this program; +// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see +// . + +/** @file experimental/scope + * This is a TS C++ Library header. + * @ingroup libfund-ts + */ + +#ifndef _GLIBCXX_EXPERIMENTAL_SCOPE +#define _GLIBCXX_EXPERIMENTAL_SCOPE 1 + +#pragma GCC system_header + +#if __cplusplus >= 202002L + +#include +#include // uncaught_exceptions +#include + +namespace std _GLIBCXX_VISIBILITY(default) +{ +_GLIBCXX_BEGIN_NAMESPACE_VERSION +namespace experimental::inline fundamentals_v3 +{ + template + concept __not_same_as = !same_as<_Tp, _Up>; + + template + concept __not_lvalue_ref = !is_lvalue_reference_v<_Tp>; + + template + class [[nodiscard]] scope_exit + { + public: + template + requires __not_same_as, scope_exit> + && constructible_from<_Ef, _Efp> + [[nodiscard]] explicit + scope_exit(_Efp&& __f) noexcept(is_nothrow_constructible_v<_Ef, _Efp&>) +#ifdef __cpp_exceptions + try +#endif + : _M_exit_function(__f) + { } +#ifdef __cpp_exceptions + catch (...) { __f(); } +#endif + + template + requires __not_same_as, scope_exit> + && constructible_from<_Ef, _Efp> + && __not_lvalue_ref<_Efp> + && is_nothrow_constructible_v<_Ef, _Efp> + explicit + scope_exit(_Efp&& __f) noexcept + : _M_exit_function(std::forward<_Efp>(__f)) + { } + + scope_exit(scope_exit&& __rhs) noexcept + requires is_nothrow_move_constructible_v<_Ef> + : _M_exit_function(std::forward<_Ef>(__rhs._M_exit_function)) + { __rhs.release(); } + + scope_exit(scope_exit&& __rhs) + noexcept(is_nothrow_copy_constructible_v<_Ef>) + requires (!is_nothrow_move_constructible_v<_Ef>) + && is_copy_constructible_v<_Ef> + : _M_exit_function(__rhs._M_exit_function) + { __rhs.release(); } + + scope_exit(const scope_exit&) = delete; + scope_exit& operator=(const scope_exit&) = delete; + scope_exit& operator=(scope_exit&&) = delete; + + ~scope_exit() noexcept(noexcept(this->_M_exit_function)) + { + if (_M_execute_on_destruction) + _M_exit_function(); + } + + void release() noexcept { _M_execute_on_destruction = false; } + + private: + [[no_unique_address]] _Ef _M_exit_function; + bool _M_execute_on_destruction = true; + }; + + template + scope_exit(_Ef) -> scope_exit<_Ef>; + + template + class [[nodiscard]] scope_fail + { + public: + template + requires __not_same_as, scope_fail> + && constructible_from<_Ef, _Efp> + explicit + scope_fail(_Efp&& __f) noexcept(is_nothrow_constructible_v<_Ef, _Efp&>) +#ifdef __cpp_exceptions + try +#endif + : _M_exit_function(__f) + { } +#ifdef __cpp_exceptions + catch (...) { __f(); } +#endif + + template + requires __not_same_as, scope_fail> + && constructible_from<_Ef, _Efp> + && __not_lvalue_ref<_Efp> + && is_nothrow_constructible_v<_Ef, _Efp> + explicit + scope_fail(_Efp&& __f) noexcept + : _M_exit_function(std::forward<_Efp>(__f)) + { } + + scope_fail(scope_fail&& __rhs) noexcept + requires is_nothrow_move_constructible_v<_Ef> + : _M_exit_function(std::forward<_Ef>(__rhs._M_exit_function)) + { __rhs.release(); } + + scope_fail(scope_fail&& __rhs) + noexcept(is_nothrow_copy_constructible_v<_Ef>) + requires (!is_nothrow_move_constructible_v<_Ef>) + && is_copy_constructible_v<_Ef> + : _M_exit_function(__rhs._M_exit_function) + { __rhs.release(); } + + scope_fail(const scope_fail&) = delete; + scope_fail& operator=(const scope_fail&) = delete; + scope_fail& operator=(scope_fail&&) = delete; + + ~scope_fail() noexcept(noexcept(this->_M_exit_function)) + { + if (std::uncaught_exceptions() > _M_uncaught_init) + _M_exit_function(); + } + + void release() noexcept { _M_uncaught_init = __INT_MAX__; } + + private: + [[no_unique_address]] _Ef _M_exit_function; + int _M_uncaught_init = std::uncaught_exceptions(); + }; + + template + scope_fail(_Ef) -> scope_fail<_Ef>; + + template + class [[nodiscard]] scope_success + { + public: + template + requires __not_same_as, scope_success> + && constructible_from<_Ef, _Efp> + explicit + scope_success(_Efp&& __f) noexcept(is_nothrow_constructible_v<_Ef, _Efp&>) + : _M_exit_function(__f) + { } + + template + requires __not_same_as, scope_success> + && constructible_from<_Ef, _Efp> + && __not_lvalue_ref<_Efp> + && is_nothrow_constructible_v<_Ef, _Efp> + explicit + scope_success(_Efp&& __f) noexcept + : _M_exit_function(std::forward<_Efp>(__f)) + { } + + scope_success(scope_success&& __rhs) noexcept + requires is_nothrow_move_constructible_v<_Ef> + : _M_exit_function(std::forward<_Ef>(__rhs._M_exit_function)) + { __rhs.release(); } + + scope_success(scope_success&& __rhs) + noexcept(is_nothrow_copy_constructible_v<_Ef>) + requires (!is_nothrow_move_constructible_v<_Ef>) + && is_copy_constructible_v<_Ef> + : _M_exit_function(__rhs._M_exit_function) + { __rhs.release(); } + + scope_success(const scope_success&) = delete; + scope_success& operator=(const scope_success&) = delete; + scope_success& operator=(scope_success&&) = delete; + + ~scope_success() noexcept(noexcept(this->_M_exit_function)) + { + if (std::uncaught_exceptions() <= _M_uncaught_init) + _M_exit_function(); + } + + void release() noexcept { _M_uncaught_init = -__INT_MAX__; } + + private: + [[no_unique_address]] _Ef _M_exit_function; + int _M_uncaught_init = std::uncaught_exceptions(); + }; + + template + scope_success(_Ef) -> scope_success<_Ef>; + + template + class [[nodiscard]] unique_resource + { + static_assert(!is_rvalue_reference_v<_Resrc>); + static_assert(!is_reference_v<_Del>); + + struct _Dummy { constexpr void release() { } }; + + template + struct _Wrap + { + template + requires is_constructible_v<_Tp, _Up> + _Wrap(_Up&&) + noexcept(is_nothrow_constructible_v<_Tp, _Up>); + + template + requires is_constructible_v<_Tp, _Up> + _Wrap(_Up&& __r, _Del2&& __d) + noexcept(is_nothrow_constructible_v<_Tp, _Up>) + : _M_t(std::forward<_Up>(__r)) + { __d.release(); } + + _Wrap() = default; + + _Wrap(_Wrap&&) = default; + + _Wrap(_Wrap&& __rhs) noexcept(is_nothrow_constructible_v<_Tp, _Tp&>) + requires (!is_nothrow_move_constructible_v<_Tp>) + : _M_t(__rhs._M_t) + { } + + _Wrap& operator=(const _Wrap&) = default; + + _Wrap& operator=(_Wrap&&) = default; + + constexpr _Tp& get() noexcept { return _M_t; } + constexpr const _Tp& get() const noexcept { return _M_t; } + + [[no_unique_address]] _Tp _M_t{}; + }; + + template + struct _Wrap<_Tp&> + { + template + requires is_constructible_v, _Up> + _Wrap(_Up&&) + noexcept(is_nothrow_constructible_v, _Up>); + + template + _Wrap(_Up&& __r, _Del2&& __d) + noexcept(is_nothrow_constructible_v, _Up>) + : _M_p(__builtin_addressof(static_cast<_Tp&>(__r))) + { __d.release(); } + + _Wrap() = delete; + + _Wrap(const _Wrap&) = default; + + _Wrap& operator=(const _Wrap&) = default; + + _Tp& get() noexcept { return *_M_p; } + const _Tp& get() const noexcept { return *_M_p; } + + _Tp* _M_p = nullptr; + }; + + using _Res1 = _Wrap<_Resrc>; + + template + requires is_constructible_v<_Tp, _Up> + && (is_nothrow_constructible_v<_Tp, _Up> + || is_constructible_v<_Tp, _Up&>) + using _Fwd_t + = __conditional_t, _Up, _Up&>; + + template + static constexpr _Fwd_t<_Tp, _Up> + _S_fwd(_Up& __u) + { return static_cast<_Fwd_t<_Tp, _Up>&&>(__u); } + + template + static constexpr auto + _S_guard(_Del2& __d, _Res2& __r) + { + if constexpr (is_nothrow_constructible_v<_Tp, _Up>) + return _Dummy{}; + else + return scope_fail{[&] { __d(__r); }}; + } + + public: + unique_resource() = default; + + template + requires requires { + typename _Fwd_t<_Res1, _Res2>; + typename _Fwd_t<_Del, _Del2>; + } + unique_resource(_Res2&& __r, _Del2&& __d) + noexcept((is_nothrow_constructible_v<_Res1, _Res2> + || is_nothrow_constructible_v<_Res1, _Res2&>) + && + (is_nothrow_constructible_v<_Del, _Del2> + || is_nothrow_constructible_v<_Del, _Del2&>)) + : _M_res(_S_fwd<_Res1, _Res2>(__r), + _S_guard<_Res1, _Res2>(__d, __r)), + _M_del(_S_fwd<_Del, _Del2>(__d), + _S_guard<_Del, _Del2>(__d, _M_res.get())), + _M_exec_on_reset(true) + { } + + unique_resource(unique_resource&& __rhs) noexcept + requires is_nothrow_move_constructible_v<_Res1> + && is_nothrow_move_constructible_v<_Del> + : _M_res(std::move(__rhs._M_res)), + _M_del(std::move(__rhs._M_del)), + _M_exec_on_reset(std::__exchange(__rhs._M_exec_on_reset, false)) + { } + + unique_resource(unique_resource&& __rhs) + requires is_nothrow_move_constructible_v<_Res1> + && (!is_nothrow_move_constructible_v<_Del>) + : _M_res(std::move(__rhs._M_res)), + _M_del(_S_fwd<_Del, _Del>(__rhs._M_del.get()), + scope_fail([&]{ + if (__rhs._M_exec_on_reset) + { + __rhs._M_del.get()(_M_res.get()); + __rhs.release(); + } + })), + _M_exec_on_reset(std::__exchange(__rhs._M_exec_on_reset, false)) + { } + + unique_resource(unique_resource&& __rhs) + requires (!is_nothrow_move_constructible_v<_Res1>) + : unique_resource(__rhs._M_res.get(), __rhs._M_del.get(), _Dummy{}) + { + if (__rhs._M_exec_on_reset) + { + _M_exec_on_reset = true; + __rhs._M_exec_on_reset = false; + } + } + + // 3.3.3.3, Destructor + ~unique_resource() { reset(); } + + // 3.3.3.4, Assignment + unique_resource& + operator=(unique_resource&& __rhs) + noexcept(is_nothrow_move_assignable_v<_Res1> + && is_nothrow_move_assignable_v<_Del>) + { + reset(); + if constexpr (is_nothrow_move_assignable_v<_Res1>) + { + if constexpr (is_nothrow_move_assignable_v<_Del>) + { + _M_res = std::move(__rhs._M_res); + _M_del = std::move(__rhs._M_del); + } + else + { + _M_del = __rhs._M_del; + _M_res = std::move(__rhs._M_res); + } + } + else + { + if constexpr (is_nothrow_move_assignable_v<_Del>) + { + _M_res = __rhs._M_res; + _M_del = std::move(__rhs._M_del); + } + else + { + _M_res = __rhs._M_res; + _M_del = __rhs._M_del; + } + } + _M_exec_on_reset = std::__exchange(__rhs._M_exec_on_reset, false); + return *this; + } + + // 3.3.3.5, Other member functions + void + reset() noexcept + { + if (_M_exec_on_reset) + { + _M_exec_on_reset = false; + _M_del.get()(_M_res.get()); + } + } + + template + void + reset(_Res2&& __r) + { + reset(); + if constexpr (is_nothrow_assignable_v<_Res1&, _Res2>) + _M_res.get() = std::forward<_Res2>(__r); + else + _M_res.get() = const_cast&>(__r); + _M_exec_on_reset = true; + } + + void + release() noexcept + { _M_exec_on_reset = false; } + + const _Resrc& + get() const noexcept + { return _M_res.get(); } + + add_lvalue_reference_t> + operator*() const noexcept + requires is_pointer_v<_Resrc> && (!is_void_v>) + { return *get(); } + + _Resrc operator->() const noexcept + requires is_pointer_v<_Resrc> + { return _M_res.get(); } + + const _Del& + get_deleter() const noexcept + { return _M_del.get(); } + + private: + [[no_unique_address]] _Res1 _M_res{}; + [[no_unique_address]] _Wrap<_Del> _M_del{}; + bool _M_exec_on_reset = false; + + template + friend unique_resource, decay_t<_Del2>> + make_unique_resource_checked(_Res2&&, const _St&, _Del2&&) + noexcept(is_nothrow_constructible_v, _Res2> + && is_nothrow_constructible_v, _Del2>); + + template + unique_resource(_Res2&& __r, _Del2&& __d, _Dummy __noop) + noexcept(is_nothrow_constructible_v<_Resrc, _Res2> + && is_nothrow_constructible_v<_Del, _Del2>) + : _M_res(std::forward<_Res2>(__r), __noop), + _M_del(std::forward<_Del>(__d), __noop) + { } + }; + + template + unique_resource(_Resrc, _Del) -> unique_resource<_Resrc, _Del>; + + template> + unique_resource, decay_t<_Del>> + make_unique_resource_checked(_Resrc&& __r, const _St& __invalid, _Del&& __d) + noexcept(is_nothrow_constructible_v, _Resrc> + && is_nothrow_constructible_v, _Del>) + { + if (__r == __invalid) + return { std::forward<_Resrc>(__r), std::forward<_Del>(__d), {} }; + return { std::forward<_Resrc>(__r), std::forward<_Del>(__d) }; + } + +} // namespace experimental::fundamentals_v3 +_GLIBCXX_END_NAMESPACE_VERSION +} // namespace std +#endif // C++20 +#endif // _GLIBCXX_EXPERIMENTAL_SCOPE diff --git a/libstdc++-v3/testsuite/experimental/scopeguard/exit.cc b/libstdc++-v3/testsuite/experimental/scopeguard/exit.cc new file mode 100644 index 00000000000..60616d1a93f --- /dev/null +++ b/libstdc++-v3/testsuite/experimental/scopeguard/exit.cc @@ -0,0 +1,300 @@ +// { dg-options "-std=gnu++20" } +// { dg-do run { target c++20 } } + +#include +#include + +int da_funk = 0; +void funk() { ++da_funk; } + +struct ThrowingCopy +{ + ThrowingCopy() = default; + ThrowingCopy(ThrowingCopy&&) noexcept(false) { VERIFY(false); } + ThrowingCopy(const ThrowingCopy&) { if (nocopy) throw 1; } + + void operator()() const noexcept { ++counter; } + + static ThrowingCopy create() noexcept { nocopy = false; return {}; } + + static bool nocopy; + static int counter; +}; + +bool ThrowingCopy::nocopy = false; +int ThrowingCopy::counter = 0; + +void +test_exit() +{ + using std::experimental::scope_exit; + + int counter = 0; + auto d = [&counter] () { ++counter; }; + + { + scope_exit e(d); + } + VERIFY( counter == 1 ); + + try + { + scope_exit e(d); + throw 1; + } + catch (int) + { + } + VERIFY( counter == 2 ); + + { + scope_exit e(d); + scope_exit e2(std::move(e)); + } + VERIFY( counter == 3 ); + + { + scope_exit e(d); + e.release(); + } + VERIFY( counter == 3 ); + + try + { + scope_exit e(d); + e.release(); + throw 1; + } + catch (int) + { + } + VERIFY( counter == 3 ); + + { + da_funk = 0; + scope_exit e(funk); + } + VERIFY( da_funk == 1 ); + + static_assert(!std::is_move_assignable_v>); + static_assert(!std::is_move_assignable_v>); + static_assert(!std::is_move_assignable_v>); + static_assert(!std::is_move_assignable_v>); + + { + ThrowingCopy::counter = 0; + try + { + scope_exit e(ThrowingCopy::create()); + ThrowingCopy::nocopy = true; + scope_exit e2(std::move(e)); + VERIFY(false); + } + catch (int) + { + } + VERIFY( ThrowingCopy::counter == 1 ); + + scope_exit e(ThrowingCopy::create()); + try + { + ThrowingCopy::nocopy = true; + scope_exit e2(std::move(e)); + VERIFY(false); + } + catch (int) + { + } + VERIFY( ThrowingCopy::counter == 1 ); + } + VERIFY( ThrowingCopy::counter == 2 ); +} + +void +test_fail() +{ + using std::experimental::scope_fail; + + int counter = 0; + auto d = [&counter] () { ++counter; }; + + { + scope_fail f(d); + } + VERIFY( counter == 0 ); + + try + { + scope_fail f(d); + throw 1; + } + catch (int) + { + } + VERIFY( counter == 1 ); + + { + scope_fail f(d); + f.release(); + } + VERIFY( counter == 1 ); + + try + { + scope_fail f(d); + scope_fail f2(std::move(f)); + throw 1; + } + catch(int) + { + } + VERIFY( counter == 2 ); + + try + { + scope_fail f(d); + f.release(); + throw 1; + } + catch (int) + { + } + VERIFY( counter == 2 ); + + try + { + da_funk = 0; + scope_fail e(funk); + throw 1; + } + catch (int) + { + } + VERIFY( da_funk == 1 ); + + static_assert(!std::is_move_assignable_v>); + static_assert(!std::is_move_assignable_v>); + static_assert(!std::is_move_assignable_v>); + static_assert(!std::is_move_assignable_v>); + + { + ThrowingCopy::counter = 0; + try + { + scope_fail f(ThrowingCopy::create()); + ThrowingCopy::nocopy = true; + scope_fail f2(std::move(f)); + VERIFY(false); + } + catch (int) + { + } + VERIFY( ThrowingCopy::counter == 1 ); + + scope_fail f(ThrowingCopy::create()); + try + { + ThrowingCopy::nocopy = true; + scope_fail f2(std::move(f)); + VERIFY(false); + } + catch (int) + { + } + VERIFY( ThrowingCopy::counter == 1 ); + } + VERIFY( ThrowingCopy::counter == 1 ); +} + +void +test_success() +{ + using std::experimental::scope_success; + + int counter = 0; + auto d = [&counter] () { ++counter; }; + + { + scope_success s(d); + } + VERIFY( counter == 1 ); + + try + { + scope_success s(d); + throw 1; + } + catch (int) + { + } + VERIFY( counter == 1 ); + + { + scope_success s(d); + scope_success s2(std::move(s)); + } + VERIFY( counter == 2 ); + + { + scope_success s(d); + s.release(); + } + VERIFY( counter == 2 ); + + try + { + scope_success s(d); + s.release(); + throw 1; + } + catch (int) + { + } + VERIFY( counter == 2 ); + + { + da_funk = 0; + scope_success e(funk); + } + VERIFY( da_funk == 1 ); + + static_assert(!std::is_move_assignable_v>); + static_assert(!std::is_move_assignable_v>); + static_assert(!std::is_move_assignable_v>); + static_assert(!std::is_move_assignable_v>); + + { + ThrowingCopy::counter = 0; + try + { + scope_success s(ThrowingCopy::create()); + ThrowingCopy::nocopy = true; + scope_success s2(std::move(s)); + VERIFY(false); + } + catch (int) + { + } + VERIFY( ThrowingCopy::counter == 0 ); + + scope_success s(ThrowingCopy::create()); + try + { + ThrowingCopy::nocopy = true; + scope_success s2(std::move(s)); + VERIFY(false); + } + catch (int) + { + } + VERIFY( ThrowingCopy::counter == 0 ); + } + VERIFY( ThrowingCopy::counter == 1 ); +} + +int main() +{ + test_exit(); + test_fail(); + test_success(); +} diff --git a/libstdc++-v3/testsuite/experimental/scopeguard/uniqueres.cc b/libstdc++-v3/testsuite/experimental/scopeguard/uniqueres.cc new file mode 100644 index 00000000000..7690572ab19 --- /dev/null +++ b/libstdc++-v3/testsuite/experimental/scopeguard/uniqueres.cc @@ -0,0 +1,360 @@ +// { dg-options "-std=gnu++20" } +// { dg-do run { target c++20 } } + +#include +#include + +using std::experimental::unique_resource; + +void +test_default_cons() +{ + struct val { int i; }; + + struct del + { + void operator()(val) const { VERIFY(false); } + int i; + }; + + static_assert( std::is_default_constructible_v> ); + static_assert( !std::is_default_constructible_v> ); + // GCC extension: + static_assert( std::is_nothrow_default_constructible_v> ); + struct exval : val { exval() noexcept(false) { } }; + static_assert( !std::is_nothrow_default_constructible_v> ); + + unique_resource res; + VERIFY( res.get().i == 0 ); // value-initialized + VERIFY( res.get_deleter().i == 0 ); // value-initialized +} + +void +test_cons() +{ + struct val { int i; }; + + struct del + { + void operator()(val v) const { VERIFY(v.i == i); } + int i; + }; + + auto r1 = unique_resource(val{}, del{}); + VERIFY( r1.get().i == 0 ); + VERIFY( r1.get_deleter().i == 0 ); + + auto r2 = unique_resource(1, 2); + VERIFY( r2.get().i == 1 ); + VERIFY( r2.get_deleter().i == 2 ); + r2.release(); + + val v{3}; + auto r3 = unique_resource(v, 3); + + static_assert( !std::is_constructible_v, val, del> ); + static_assert( !std::is_constructible_v, int, del> ); + static_assert( !std::is_constructible_v, const val&, del> ); + + del d4{4}; + auto r4 = unique_resource(std::ref(v), std::ref(d4)); + --d4.i; + + static_assert( std::is_same_v, + std::reference_wrapper>> ); + static_assert( !std::is_constructible_v ); + + int counter = 0, dcounter = 99; + { + unique_resource r(std::ref(counter), + [&dcounter] (int& i) { ++dcounter; ++i; }); + } + VERIFY( counter == 1 ); + VERIFY( dcounter == 100 ); + + { + struct NothrowMove + { + NothrowMove() noexcept { } + NothrowMove(NothrowMove&&) noexcept(true) { } + NothrowMove(const NothrowMove&) { throw 1; } + }; + + unique_resource r(NothrowMove{}, + [&dcounter] (NothrowMove&) { ++dcounter; }); + } + VERIFY( dcounter == 101 ); + + { + struct ThrowOnCopy + { + ThrowOnCopy() noexcept { } + ThrowOnCopy(ThrowOnCopy&&) noexcept(false) { VERIFY(false); }; + ThrowOnCopy(const ThrowOnCopy&) { throw 1; } + explicit ThrowOnCopy(val) noexcept(false) { VERIFY(false); } + explicit ThrowOnCopy(val&) noexcept(false) { } + }; + auto d = [&dcounter] (auto&) { ++dcounter; }; + + unique_resource r(val(1), d); // uses ThrowOnCopy(val&) + + try { + unique_resource r(ThrowOnCopy{}, d); // uses copy constructor + VERIFY( false ); + } catch (int) { + VERIFY( dcounter == 102 ); + } + } + VERIFY( dcounter == 103 ); + + { + struct CopyVal + { + explicit CopyVal(const val& v) : i(v.i) { } + int i; + }; + + struct Del + { + void operator()(const val&) { VERIFY(false); } + void operator()(const CopyVal& c) { ref = c.i; } + int& ref; + }; + + struct CopyDel + { + explicit CopyDel(Del&&) noexcept(false) { VERIFY(false); } + explicit CopyDel(const Del&) noexcept(false) { throw 1; } + void operator()(const val&) = delete; + void operator()(const CopyVal&) { VERIFY(false); } + }; + + try { + // CopyVal is constructed from val(11), then initializing CopyDel throws. + // The CopyVal member is passed to the Del argument to be freed. + unique_resource r(val(11), Del{dcounter}); + VERIFY( false ); + } catch (int) { + VERIFY( dcounter == 11 ); + } + } +} + +void +test_move_cons() +{ + { + struct Del + { + void operator()(int) const { VERIFY(false); } + }; + + unique_resource r0; + auto rr0 = std::move(r0); + VERIFY( r0.get() == 0 ); + VERIFY( rr0.get() == 0 ); + + struct DelThrowingCopy + { + DelThrowingCopy() = default; + DelThrowingCopy(const DelThrowingCopy&) { throw 1; } + void operator()(int) const { VERIFY(false); } + }; + + unique_resource r1; + try { + auto rr1 = std::move(r1); // Initializing deleter throws. + VERIFY( false ); + } catch (int) { + } + } + + { + struct Res + { + Res() = default; + Res(Res&& r) noexcept : moved(r.moved) { r.moved = true; } + Res(Res& r) : moved(r.moved) { } + bool moved = false; + }; + + unique_resource r(Res{}, [](const auto&) { }); + auto rr = std::move(r); + VERIFY( r.get().moved == true ); + VERIFY( rr.get().moved == false ); + } + + { + struct Res2 + { + Res2() = default; + Res2(Res2&& r) noexcept(false) : moved(r.moved) { r.moved = false; } + Res2(Res2& r) : moved(r.moved) { } + bool moved = false; + }; + + unique_resource r2(Res2{}, [](const auto&) { }); + auto rr2 = std::move(r2); + VERIFY( r2.get().moved == false ); + VERIFY( rr2.get().moved == false ); + } + + { + struct ThrowingCopy + { + ThrowingCopy(int) { } + ThrowingCopy(const ThrowingCopy&) { throw 1; } + }; + + int dcounter = 0; + { + auto d = [&dcounter] (const auto&) { ++dcounter; }; + unique_resource r(1, d); + try { + auto rr = std::move(r); // Ownership of resource left with 'r' + VERIFY(false); + } catch (int) { + VERIFY( dcounter == 0 ); + } + } + VERIFY( dcounter == 1 ); + } +} + +int called1 = 0; + +void +test_assign() +{ + struct ThrowingDel + { + ThrowingDel() = default; + ThrowingDel(int& called) : called(called) { } + ThrowingDel(const ThrowingDel&) = default; + ThrowingDel& operator=(const ThrowingDel&) { throw 1; } + + void operator()(int i) const noexcept { ++called; } + int& called = called1; + }; + + int called2 = 0; + { + unique_resource r1; + VERIFY( r1.get() == 0 ); + unique_resource r2(2, ThrowingDel{called2}); + VERIFY( r2.get() == 2 ); + try + { + r1 = std::move(r2); + VERIFY( false ); + } + catch (int) + { + } + VERIFY( called1 == 0 ); // r1.reset() was called, but did nothing. + VERIFY( called2 == 0 ); // r2.reset() not called. + VERIFY( r1.get() == 0 ); + VERIFY( r2.get() == 2 ); + } + VERIFY( called1 == 0 ); // r1.reset() was called, but did nothing. + VERIFY( called2 == 1 ); // r2 destructor invoked its deleter. +} + +void +test_modifiers() +{ + int dcounter = 0; + auto d = [&dcounter] (int i) { dcounter += i; }; + unique_resource r(1, d); + r.reset(); + VERIFY( dcounter == 1 ); + r.reset(2); + VERIFY( dcounter == 1 ); + r.release(); + VERIFY( dcounter == 1 ); + r.release(); + VERIFY( dcounter == 1 ); + r.reset(3); + VERIFY( dcounter == 1 ); + r.reset(4); + VERIFY( dcounter == 4 ); +} + +template concept has_star = requires (T& t) { *t; }; +template concept has_arrow = requires (T& t) { t.operator->(); }; + +void +test_observers() +{ + struct D { void operator()(int* p) const noexcept { delete p; } }; + int* p = new int(3); + unique_resource r(p, D{}); + VERIFY( r.get() == p ); + VERIFY( *r == 3 ); + VERIFY( r.operator->() == p ); + (void) r.get_deleter(); + + using R1 = unique_resource; + static_assert( ! has_star ); + static_assert( ! has_arrow ); + using R2 = unique_resource; + static_assert( ! has_star ); + static_assert( has_arrow ); +} + +void +test_make_checked() +{ + struct Boolish { + explicit operator bool() const noexcept { return val; } + bool val; + }; + + using std::experimental::make_unique_resource_checked; + + { + struct ThrowingCopy + { + ThrowingCopy(int i) : val(i) { } + ThrowingCopy(const ThrowingCopy&) { throw 1; } + Boolish operator==(int i) const noexcept { return {i == val}; } + int val; + }; + + int dcounter = 0; + auto d = [&dcounter] (const auto&) { ++dcounter; }; + + try + { + (void) make_unique_resource_checked(ThrowingCopy(1), 0, d); + VERIFY(false); + } + catch (int) + { + VERIFY(dcounter == 1); + } + + dcounter = 0; + try + { + (void) make_unique_resource_checked(ThrowingCopy(1), 1, d); + VERIFY(false); + } + catch (int) + { + VERIFY(dcounter == 0); + } + } +} + +int main() +{ + test_default_cons(); + test_cons(); + test_move_cons(); + test_assign(); + test_modifiers(); + test_observers(); + test_make_checked(); +} -- 2.37.1