* [committed] libstdc++: Implement <experimental/scope> from LFTSv3
@ 2022-08-05 13:59 Jonathan Wakely
2022-08-05 14:20 ` [committed] libstdc++: Add feature test macro for <experimental/scope> Jonathan Wakely
0 siblings, 1 reply; 2+ messages in thread
From: Jonathan Wakely @ 2022-08-05 13:59 UTC (permalink / raw)
To: libstdc++, gcc-patches
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 @@
+// <experimental/scope> -*- 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
+// <http://www.gnu.org/licenses/>.
+
+/** @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 <concepts>
+#include <exception> // uncaught_exceptions
+#include <bits/refwrap.h>
+
+namespace std _GLIBCXX_VISIBILITY(default)
+{
+_GLIBCXX_BEGIN_NAMESPACE_VERSION
+namespace experimental::inline fundamentals_v3
+{
+ template<typename _Tp, typename _Up>
+ concept __not_same_as = !same_as<_Tp, _Up>;
+
+ template<typename _Tp>
+ concept __not_lvalue_ref = !is_lvalue_reference_v<_Tp>;
+
+ template<typename _Ef>
+ class [[nodiscard]] scope_exit
+ {
+ public:
+ template<typename _Efp>
+ requires __not_same_as<remove_cvref_t<_Efp>, 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<typename _Efp>
+ requires __not_same_as<remove_cvref_t<_Efp>, 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<typename _Ef>
+ scope_exit(_Ef) -> scope_exit<_Ef>;
+
+ template<typename _Ef>
+ class [[nodiscard]] scope_fail
+ {
+ public:
+ template<typename _Efp>
+ requires __not_same_as<remove_cvref_t<_Efp>, 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<typename _Efp>
+ requires __not_same_as<remove_cvref_t<_Efp>, 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<typename _Ef>
+ scope_fail(_Ef) -> scope_fail<_Ef>;
+
+ template<typename _Ef>
+ class [[nodiscard]] scope_success
+ {
+ public:
+ template<typename _Efp>
+ requires __not_same_as<remove_cvref_t<_Efp>, scope_success>
+ && constructible_from<_Ef, _Efp>
+ explicit
+ scope_success(_Efp&& __f) noexcept(is_nothrow_constructible_v<_Ef, _Efp&>)
+ : _M_exit_function(__f)
+ { }
+
+ template<typename _Efp>
+ requires __not_same_as<remove_cvref_t<_Efp>, 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<typename _Ef>
+ scope_success(_Ef) -> scope_success<_Ef>;
+
+ template<typename _Resrc, typename _Del>
+ class [[nodiscard]] unique_resource
+ {
+ static_assert(!is_rvalue_reference_v<_Resrc>);
+ static_assert(!is_reference_v<_Del>);
+
+ struct _Dummy { constexpr void release() { } };
+
+ template<typename _Tp>
+ struct _Wrap
+ {
+ template<typename _Up>
+ requires is_constructible_v<_Tp, _Up>
+ _Wrap(_Up&&)
+ noexcept(is_nothrow_constructible_v<_Tp, _Up>);
+
+ template<typename _Up, typename _Del2>
+ 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<typename _Tp>
+ struct _Wrap<_Tp&>
+ {
+ template<typename _Up>
+ requires is_constructible_v<reference_wrapper<_Tp>, _Up>
+ _Wrap(_Up&&)
+ noexcept(is_nothrow_constructible_v<reference_wrapper<_Tp>, _Up>);
+
+ template<typename _Up, typename _Del2>
+ _Wrap(_Up&& __r, _Del2&& __d)
+ noexcept(is_nothrow_constructible_v<reference_wrapper<_Tp>, _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<typename _Tp, typename _Up>
+ requires is_constructible_v<_Tp, _Up>
+ && (is_nothrow_constructible_v<_Tp, _Up>
+ || is_constructible_v<_Tp, _Up&>)
+ using _Fwd_t
+ = __conditional_t<is_nothrow_constructible_v<_Tp, _Up>, _Up, _Up&>;
+
+ template<typename _Tp, typename _Up>
+ static constexpr _Fwd_t<_Tp, _Up>
+ _S_fwd(_Up& __u)
+ { return static_cast<_Fwd_t<_Tp, _Up>&&>(__u); }
+
+ template<typename _Tp, typename _Up, typename _Del2, typename _Res2>
+ 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<typename _Res2, typename _Del2>
+ 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<typename _Res2>
+ 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<const remove_reference_t<_Res2>&>(__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<remove_pointer_t<_Resrc>>
+ operator*() const noexcept
+ requires is_pointer_v<_Resrc> && (!is_void_v<remove_pointer_t<_Resrc>>)
+ { 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<typename _Res2, typename _Del2, typename _St>
+ friend unique_resource<decay_t<_Res2>, decay_t<_Del2>>
+ make_unique_resource_checked(_Res2&&, const _St&, _Del2&&)
+ noexcept(is_nothrow_constructible_v<decay_t<_Res2>, _Res2>
+ && is_nothrow_constructible_v<decay_t<_Del2>, _Del2>);
+
+ template<typename _Res2, typename _Del2>
+ 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<typename _Resrc, typename _Del>
+ unique_resource(_Resrc, _Del) -> unique_resource<_Resrc, _Del>;
+
+ template<typename _Resrc, typename _Del, typename _St = decay_t<_Resrc>>
+ unique_resource<decay_t<_Resrc>, decay_t<_Del>>
+ make_unique_resource_checked(_Resrc&& __r, const _St& __invalid, _Del&& __d)
+ noexcept(is_nothrow_constructible_v<decay_t<_Resrc>, _Resrc>
+ && is_nothrow_constructible_v<decay_t<_Del>, _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 <experimental/scope>
+#include <testsuite_hooks.h>
+
+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<void(&)()> e(funk);
+ }
+ VERIFY( da_funk == 1 );
+
+ static_assert(!std::is_move_assignable_v<scope_exit<void(*)()>>);
+ static_assert(!std::is_move_assignable_v<scope_exit<void(&)()>>);
+ static_assert(!std::is_move_assignable_v<scope_exit<ThrowingCopy>>);
+ static_assert(!std::is_move_assignable_v<scope_exit<decltype(d)>>);
+
+ {
+ ThrowingCopy::counter = 0;
+ try
+ {
+ scope_exit<ThrowingCopy> e(ThrowingCopy::create());
+ ThrowingCopy::nocopy = true;
+ scope_exit<ThrowingCopy> e2(std::move(e));
+ VERIFY(false);
+ }
+ catch (int)
+ {
+ }
+ VERIFY( ThrowingCopy::counter == 1 );
+
+ scope_exit<ThrowingCopy> e(ThrowingCopy::create());
+ try
+ {
+ ThrowingCopy::nocopy = true;
+ scope_exit<ThrowingCopy> 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<void(&)()> e(funk);
+ throw 1;
+ }
+ catch (int)
+ {
+ }
+ VERIFY( da_funk == 1 );
+
+ static_assert(!std::is_move_assignable_v<scope_fail<void(*)()>>);
+ static_assert(!std::is_move_assignable_v<scope_fail<void(&)()>>);
+ static_assert(!std::is_move_assignable_v<scope_fail<ThrowingCopy>>);
+ static_assert(!std::is_move_assignable_v<scope_fail<decltype(d)>>);
+
+ {
+ ThrowingCopy::counter = 0;
+ try
+ {
+ scope_fail<ThrowingCopy> f(ThrowingCopy::create());
+ ThrowingCopy::nocopy = true;
+ scope_fail<ThrowingCopy> f2(std::move(f));
+ VERIFY(false);
+ }
+ catch (int)
+ {
+ }
+ VERIFY( ThrowingCopy::counter == 1 );
+
+ scope_fail<ThrowingCopy> f(ThrowingCopy::create());
+ try
+ {
+ ThrowingCopy::nocopy = true;
+ scope_fail<ThrowingCopy> 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<void(&)()> e(funk);
+ }
+ VERIFY( da_funk == 1 );
+
+ static_assert(!std::is_move_assignable_v<scope_success<void(*)()>>);
+ static_assert(!std::is_move_assignable_v<scope_success<void(&)()>>);
+ static_assert(!std::is_move_assignable_v<scope_success<ThrowingCopy>>);
+ static_assert(!std::is_move_assignable_v<scope_success<decltype(d)>>);
+
+ {
+ ThrowingCopy::counter = 0;
+ try
+ {
+ scope_success<ThrowingCopy> s(ThrowingCopy::create());
+ ThrowingCopy::nocopy = true;
+ scope_success<ThrowingCopy> s2(std::move(s));
+ VERIFY(false);
+ }
+ catch (int)
+ {
+ }
+ VERIFY( ThrowingCopy::counter == 0 );
+
+ scope_success<ThrowingCopy> s(ThrowingCopy::create());
+ try
+ {
+ ThrowingCopy::nocopy = true;
+ scope_success<ThrowingCopy> 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 <experimental/scope>
+#include <testsuite_hooks.h>
+
+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<unique_resource<val, del>> );
+ static_assert( !std::is_default_constructible_v<unique_resource<val&, del>> );
+ // GCC extension:
+ static_assert( std::is_nothrow_default_constructible_v<unique_resource<val, del>> );
+ struct exval : val { exval() noexcept(false) { } };
+ static_assert( !std::is_nothrow_default_constructible_v<unique_resource<exval, del>> );
+
+ unique_resource<val, del> 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>(val{}, del{});
+ VERIFY( r1.get().i == 0 );
+ VERIFY( r1.get_deleter().i == 0 );
+
+ auto r2 = unique_resource<val, del>(1, 2);
+ VERIFY( r2.get().i == 1 );
+ VERIFY( r2.get_deleter().i == 2 );
+ r2.release();
+
+ val v{3};
+ auto r3 = unique_resource<val&, del>(v, 3);
+
+ static_assert( !std::is_constructible_v<unique_resource<val&, del>, val, del> );
+ static_assert( !std::is_constructible_v<unique_resource<val&, del>, int, del> );
+ static_assert( !std::is_constructible_v<unique_resource<val&, del>, const val&, del> );
+
+ del d4{4};
+ auto r4 = unique_resource(std::ref(v), std::ref(d4));
+ --d4.i;
+
+ static_assert( std::is_same_v<decltype(r4),
+ unique_resource<std::reference_wrapper<val>,
+ std::reference_wrapper<del>>> );
+ static_assert( !std::is_constructible_v<decltype(r4), val, del> );
+
+ 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<CopyVal, CopyDel> 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<int, Del> 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<int, DelThrowingCopy> 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<ThrowingCopy, decltype(d)> 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<int, ThrowingDel> r1;
+ VERIFY( r1.get() == 0 );
+ unique_resource<int, ThrowingDel> 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<int, decltype(d)> 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<typename T> concept has_star = requires (T& t) { *t; };
+template<typename T> 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<int*, D> r(p, D{});
+ VERIFY( r.get() == p );
+ VERIFY( *r == 3 );
+ VERIFY( r.operator->() == p );
+ (void) r.get_deleter();
+
+ using R1 = unique_resource<int, void(*)(int)>;
+ static_assert( ! has_star<R1> );
+ static_assert( ! has_arrow<R1> );
+ using R2 = unique_resource<const void*, void(*)(const void*)>;
+ static_assert( ! has_star<R2> );
+ static_assert( has_arrow<R2> );
+}
+
+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
^ permalink raw reply [flat|nested] 2+ messages in thread
* [committed] libstdc++: Add feature test macro for <experimental/scope>
2022-08-05 13:59 [committed] libstdc++: Implement <experimental/scope> from LFTSv3 Jonathan Wakely
@ 2022-08-05 14:20 ` Jonathan Wakely
0 siblings, 0 replies; 2+ messages in thread
From: Jonathan Wakely @ 2022-08-05 14:20 UTC (permalink / raw)
To: libstdc++, gcc-patches
I forgot to add this macro to the new header.
Tested x86_64-linux, pushed to trunk.
-- >8 --
libstdc++-v3/ChangeLog:
* include/experimental/scope (__cpp_lib_experimental_scope):
Define.
* testsuite/experimental/scopeguard/uniqueres.cc: Check macro.
---
libstdc++-v3/include/experimental/scope | 2 ++
libstdc++-v3/testsuite/experimental/scopeguard/uniqueres.cc | 6 ++++++
2 files changed, 8 insertions(+)
diff --git a/libstdc++-v3/include/experimental/scope b/libstdc++-v3/include/experimental/scope
index 37a57b38af7..208fadc513e 100644
--- a/libstdc++-v3/include/experimental/scope
+++ b/libstdc++-v3/include/experimental/scope
@@ -43,6 +43,8 @@ namespace std _GLIBCXX_VISIBILITY(default)
_GLIBCXX_BEGIN_NAMESPACE_VERSION
namespace experimental::inline fundamentals_v3
{
+#define __cpp_lib_experimental_scope 201902
+
template<typename _Tp, typename _Up>
concept __not_same_as = !same_as<_Tp, _Up>;
diff --git a/libstdc++-v3/testsuite/experimental/scopeguard/uniqueres.cc b/libstdc++-v3/testsuite/experimental/scopeguard/uniqueres.cc
index 7690572ab19..fe9d6ee7cfc 100644
--- a/libstdc++-v3/testsuite/experimental/scopeguard/uniqueres.cc
+++ b/libstdc++-v3/testsuite/experimental/scopeguard/uniqueres.cc
@@ -4,6 +4,12 @@
#include <experimental/scope>
#include <testsuite_hooks.h>
+#ifndef __cpp_lib_experimental_scope
+# error Feature-test macro is not defined.
+#elif __cpp_lib_experimental_scope < 201902
+# error Feature-test macro has bad value.
+#endif
+
using std::experimental::unique_resource;
void
--
2.37.1
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2022-08-05 14:20 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-08-05 13:59 [committed] libstdc++: Implement <experimental/scope> from LFTSv3 Jonathan Wakely
2022-08-05 14:20 ` [committed] libstdc++: Add feature test macro for <experimental/scope> Jonathan Wakely
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).