public inbox for libstdc++@gcc.gnu.org
 help / color / mirror / Atom feed
* [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).