From patchwork Sun Mar 27 17:58:19 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jonathan Wakely X-Patchwork-Id: 52388 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 55DC33858C74 for ; Sun, 27 Mar 2022 17:59:06 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 55DC33858C74 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gcc.gnu.org; s=default; t=1648403946; bh=CUp9uWisgpKcFAC8ptslAdcGtDz4VNJyQcTUMOEVcKw=; h=To:Subject:Date:List-Id:List-Unsubscribe:List-Archive:List-Post: List-Help:List-Subscribe:From:Reply-To:From; b=F9NQoghRfrlyVt/l4Z428WmQicrPu4hZpabaiKUSE+PexOPqwhapY/i9MoEmxONIV /pgfjbEHjHXd3qMehTzUbRqAu4j6uM0nbZTVFcwDsqoBxQRomMJqy9nz1Pzw4AqlWI E1VhMaxrNw0wd5PbV1sI/1sfxa3sDCfbsz36ajw8= X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTPS id 7FE963858C52 for ; Sun, 27 Mar 2022 17:58:24 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org 7FE963858C52 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-75-skMjlyQ5NbaQ_IoyN-SnSg-1; Sun, 27 Mar 2022 13:58:21 -0400 X-MC-Unique: skMjlyQ5NbaQ_IoyN-SnSg-1 Received: from smtp.corp.redhat.com (int-mx01.intmail.prod.int.rdu2.redhat.com [10.11.54.1]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id B5D51801585; Sun, 27 Mar 2022 17:58:20 +0000 (UTC) Received: from localhost (unknown [10.33.36.3]) by smtp.corp.redhat.com (Postfix) with ESMTP id 224CE40CF8EA; Sun, 27 Mar 2022 17:58:20 +0000 (UTC) To: libstdc++@gcc.gnu.org, gcc-patches@gcc.gnu.org Subject: [committed] libstdc++: Define std::expected for C++23 (P0323R12) Date: Sun, 27 Mar 2022 18:58:19 +0100 Message-Id: <20220327175819.1422085-1-jwakely@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.84 on 10.11.54.1 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-12.1 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_LOW, RCVD_IN_MSPIKE_H4, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP, T_SCC_BODY_TEXT_LINE autolearn=ham autolearn_force=no version=3.4.4 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on server2.sourceware.org X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Jonathan Wakely via Gcc-patches From: Jonathan Wakely Reply-To: Jonathan Wakely Errors-To: gcc-patches-bounces+patchwork=sourceware.org@gcc.gnu.org Sender: "Gcc-patches" Tested powerpc64le-linux, pushed to trunk. It's late in stage 4 to be adding new features, but this is C++23-only so entirely experimental, and not defined for the std-gnu++17 default (or even -std=gnu++20). It does mean that the old (very old) std::unexpected function is no longer available in C++23 mode, but it's not even been part of the standard since C++17, and carries the deprecated attribute in libstdc++, so I don't see any problem with making the change for C++23. We might even want to consider removing it for C++20 so there's a "gap" between the old meaning of the name as a function, and the new meaning as a class template. -- >8 -- Because this adds a new class template called std::unexpected, we have to stop declaring the std::unexpected() function (which was deprecated in C++11 and removed in C++17). libstdc++-v3/ChangeLog: * doc/doxygen/user.cfg.in: Add new header. * include/Makefile.am: Likewise. * include/Makefile.in: Regenerate. * include/precompiled/stdc++.h: Add new header. * include/std/version (__cpp_lib_expected): Define. * libsupc++/exception [__cplusplus > 202002] (unexpected) (unexpected_handler, set_unexpected): Do not declare for C++23. * include/std/expected: New file. * testsuite/20_util/expected/assign.cc: New test. * testsuite/20_util/expected/cons.cc: New test. * testsuite/20_util/expected/illformed_neg.cc: New test. * testsuite/20_util/expected/observers.cc: New test. * testsuite/20_util/expected/requirements.cc: New test. * testsuite/20_util/expected/swap.cc: New test. * testsuite/20_util/expected/synopsis.cc: New test. * testsuite/20_util/expected/unexpected.cc: New test. * testsuite/20_util/expected/version.cc: New test. --- libstdc++-v3/doc/doxygen/user.cfg.in | 1 + libstdc++-v3/include/Makefile.am | 1 + libstdc++-v3/include/Makefile.in | 1 + libstdc++-v3/include/precompiled/stdc++.h | 1 + libstdc++-v3/include/std/expected | 1240 +++++++++++++++++ libstdc++-v3/include/std/version | 1 + libstdc++-v3/libsupc++/exception | 2 +- .../testsuite/20_util/expected/assign.cc | 321 +++++ .../testsuite/20_util/expected/cons.cc | 175 +++ .../20_util/expected/illformed_neg.cc | 67 + .../testsuite/20_util/expected/observers.cc | 209 +++ .../20_util/expected/requirements.cc | 129 ++ .../testsuite/20_util/expected/swap.cc | 57 + .../testsuite/20_util/expected/synopsis.cc | 21 + .../testsuite/20_util/expected/unexpected.cc | 80 ++ .../testsuite/20_util/expected/version.cc | 10 + 16 files changed, 2315 insertions(+), 1 deletion(-) create mode 100644 libstdc++-v3/include/std/expected create mode 100644 libstdc++-v3/testsuite/20_util/expected/assign.cc create mode 100644 libstdc++-v3/testsuite/20_util/expected/cons.cc create mode 100644 libstdc++-v3/testsuite/20_util/expected/illformed_neg.cc create mode 100644 libstdc++-v3/testsuite/20_util/expected/observers.cc create mode 100644 libstdc++-v3/testsuite/20_util/expected/requirements.cc create mode 100644 libstdc++-v3/testsuite/20_util/expected/swap.cc create mode 100644 libstdc++-v3/testsuite/20_util/expected/synopsis.cc create mode 100644 libstdc++-v3/testsuite/20_util/expected/unexpected.cc create mode 100644 libstdc++-v3/testsuite/20_util/expected/version.cc diff --git a/libstdc++-v3/doc/doxygen/user.cfg.in b/libstdc++-v3/doc/doxygen/user.cfg.in index 2f15f2c1b82..85955f88390 100644 --- a/libstdc++-v3/doc/doxygen/user.cfg.in +++ b/libstdc++-v3/doc/doxygen/user.cfg.in @@ -858,6 +858,7 @@ INPUT = @srcdir@/doc/doxygen/doxygroups.cc \ include/concepts \ include/condition_variable \ include/deque \ + include/expected \ include/filesystem \ include/forward_list \ include/fstream \ diff --git a/libstdc++-v3/include/Makefile.am b/libstdc++-v3/include/Makefile.am index 43f7f9f240d..77eea7d61e8 100644 --- a/libstdc++-v3/include/Makefile.am +++ b/libstdc++-v3/include/Makefile.am @@ -42,6 +42,7 @@ std_headers = \ ${std_srcdir}/coroutine \ ${std_srcdir}/deque \ ${std_srcdir}/execution \ + ${std_srcdir}/expected \ ${std_srcdir}/filesystem \ ${std_srcdir}/forward_list \ ${std_srcdir}/fstream \ diff --git a/libstdc++-v3/include/Makefile.in b/libstdc++-v3/include/Makefile.in index 88391a44d33..01bf3e0eb32 100644 --- a/libstdc++-v3/include/Makefile.in +++ b/libstdc++-v3/include/Makefile.in @@ -400,6 +400,7 @@ std_headers = \ ${std_srcdir}/coroutine \ ${std_srcdir}/deque \ ${std_srcdir}/execution \ + ${std_srcdir}/expected \ ${std_srcdir}/filesystem \ ${std_srcdir}/forward_list \ ${std_srcdir}/fstream \ diff --git a/libstdc++-v3/include/precompiled/stdc++.h b/libstdc++-v3/include/precompiled/stdc++.h index dc94a9c471f..6d6d2ad7c4c 100644 --- a/libstdc++-v3/include/precompiled/stdc++.h +++ b/libstdc++-v3/include/precompiled/stdc++.h @@ -153,5 +153,6 @@ #endif #if __cplusplus > 202002L +#include #include #endif diff --git a/libstdc++-v3/include/std/expected b/libstdc++-v3/include/std/expected new file mode 100644 index 00000000000..39d07cda4a9 --- /dev/null +++ b/libstdc++-v3/include/std/expected @@ -0,0 +1,1240 @@ +// -*- 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 include/expected + * This is a Standard C++ Library header. + */ + +#ifndef _GLIBCXX_EXPECTED +#define _GLIBCXX_EXPECTED + +#pragma GCC system_header + +#if __cplusplus > 202002L && __cpp_concepts >= 202002L + +#include +#include // exception +#include // construct_at +#include // in_place_t + +namespace std _GLIBCXX_VISIBILITY(default) +{ +_GLIBCXX_BEGIN_NAMESPACE_VERSION + + /** + * @defgroup expected_values Expected values + * @addtogroup utilities + * @since C++23 + * @{ + */ + +#define __cpp_lib_expected 202202L + + /// Discriminated union that holds an expected value or an error value. + /** + * @since C++23 + */ + template + class expected; + + /// Wrapper type used to pass an error value to a `std::expected`. + /** + * @since C++23 + */ + template + class unexpected; + + /// Exception thrown by std::expected when the value() is not present. + /** + * @since C++23 + */ + template + class bad_expected_access; + + template<> + class bad_expected_access : public exception + { + protected: + bad_expected_access() noexcept { } + bad_expected_access(const bad_expected_access&) = default; + bad_expected_access(bad_expected_access&&) = default; + bad_expected_access& operator=(const bad_expected_access&) = default; + bad_expected_access& operator=(bad_expected_access&&) = default; + ~bad_expected_access() = default; + + public: + + [[nodiscard]] + const char* + what() const noexcept override + { return "bad access to std::expected without expected value"; } + }; + + template + class bad_expected_access : public bad_expected_access { + public: + explicit + bad_expected_access(_Er __e) : _M_val(__e) { } + + // XXX const char* what() const noexcept override; + + [[nodiscard]] + _Er& + error() & noexcept + { return _M_val; } + + [[nodiscard]] + const _Er& + error() const & noexcept + { return _M_val; } + + [[nodiscard]] + _Er&& + error() && noexcept + { return std::move(_M_val); } + + [[nodiscard]] + const _Er&& + error() const && noexcept + { return std::move(_M_val); } + + private: + _Er _M_val; + }; + + /// Tag type for constructing unexpected values in a std::expected + /** + * @since C++23 + */ + struct unexpect_t + { + explicit unexpect_t() = default; + }; + + /// Tag for constructing unexpected values in a std::expected + /** + * @since C++23 + */ + inline constexpr unexpect_t unexpect{}; + +/// @cond undoc +namespace __expected +{ + template + constexpr bool __is_expected = false; + template + constexpr bool __is_expected> = true; + + template + constexpr bool __is_unexpected = false; + template + constexpr bool __is_unexpected> = true; + + template + concept __can_be_unexpected + = is_object_v<_Er> && (!is_array_v<_Er>) + && (!__expected::__is_unexpected<_Er>) + && (!is_const_v<_Er>) && (!is_volatile_v<_Er>); +} +/// @endcond + + template + class unexpected + { + static_assert( __expected::__can_be_unexpected<_Er> ); + + public: + constexpr unexpected(const unexpected&) = default; + constexpr unexpected(unexpected&&) = default; + + template + requires (!is_same_v, unexpected>) + && (!is_same_v, in_place_t>) + && is_constructible_v<_Er, _Err> + constexpr explicit + unexpected(_Err&& __e) + noexcept(is_nothrow_constructible_v<_Er, _Err>) + : _M_val(std::forward<_Err>(__e)) + { } + + template + requires is_constructible_v<_Er, _Args...> + constexpr explicit + unexpected(in_place_t, _Args&&... __args) + noexcept(is_nothrow_constructible_v<_Er, _Args...>) + : _M_val(std::forward<_Args>(__args)...) + { } + + template + requires is_constructible_v<_Er, initializer_list<_Up>&, _Args...> + constexpr explicit + unexpected(in_place_t, initializer_list<_Up> __il, _Args&&... __args) + noexcept(is_nothrow_constructible_v<_Er, initializer_list<_Up>&, + _Args...>) + : _M_val(__il, std::forward<_Args>(__args)...) + { } + + constexpr unexpected& operator=(const unexpected&) = default; + constexpr unexpected& operator=(unexpected&&) = default; + + + [[nodiscard]] + constexpr const _Er& + error() const & noexcept { return _M_val; } + + [[nodiscard]] + constexpr _Er& + error() & noexcept { return _M_val; } + + [[nodiscard]] + constexpr const _Er&& + error() const && noexcept { return std::move(_M_val); } + + [[nodiscard]] + constexpr _Er&& + error() && noexcept { return std::move(_M_val); } + + constexpr void + swap(unexpected& __other) noexcept(is_nothrow_swappable_v<_Er>) + { + static_assert( is_swappable_v<_Er> ); + using std::swap; + swap(_M_val, __other._M_val); + } + + template + [[nodiscard]] + friend constexpr bool + operator==(const unexpected& __x, const unexpected<_Err>& __y) + { return __x._M_val == __y.error(); } + + friend constexpr void + swap(unexpected& __x, unexpected& __y) + noexcept(noexcept(__x.swap(__y))) + requires requires {__x.swap(__y);} + { __x.swap(__y); } + + private: + _Er _M_val; + }; + + template unexpected(_Er) -> unexpected<_Er>; + +/// @cond undoc +namespace __expected +{ + template + struct _Guard + { + static_assert( is_nothrow_move_constructible_v<_Tp> ); + + constexpr explicit + _Guard(_Tp& __x) + : _M_guarded(__builtin_addressof(__x)), _M_tmp(std::move(__x)) // nothrow + { std::destroy_at(_M_guarded); } + + constexpr + ~_Guard() + { + if (_M_guarded) [[unlikely]] + std::construct_at(_M_guarded, std::move(_M_tmp)); + } + + _Guard(const _Guard&) = delete; + _Guard& operator=(const _Guard&) = delete; + + constexpr _Tp&& + release() noexcept + { + _M_guarded = nullptr; + return std::move(_M_tmp); + } + + private: + _Tp* _M_guarded; + _Tp _M_tmp; + }; + + // reinit-expected helper from [expected.object.assign] + template + constexpr void + __reinit(_Tp* __newval, _Up* __oldval, _Vp&& __arg) + noexcept(is_nothrow_constructible_v<_Tp, _Vp>) + { + if constexpr (is_nothrow_constructible_v<_Tp, _Vp>) + { + std::destroy_at(__oldval); + std::construct_at(__newval, std::forward<_Vp>(__arg)); + } + else if constexpr (is_nothrow_move_constructible_v<_Tp>) + { + _Tp __tmp(std::forward<_Vp>(__arg)); // might throw + std::destroy_at(__oldval); + std::construct_at(__newval, std::move(__tmp)); + } + else + { + _Guard<_Up> __guard(*__oldval); + std::construct_at(__newval, std::forward<_Vp>(__arg)); // might throw + __guard.release(); + } + } +} +/// @endcond + + template + class expected + { + static_assert( ! is_reference_v<_Tp> ); + static_assert( ! is_function_v<_Tp> ); + static_assert( ! is_same_v, in_place_t> ); + static_assert( ! is_same_v, unexpect_t> ); + static_assert( ! __expected::__is_unexpected> ); + static_assert( __expected::__can_be_unexpected<_Er> ); + + template> + static constexpr bool __cons_from_expected + = __or_v&>, + is_constructible<_Tp, expected<_Up, _Err>>, + is_constructible<_Tp, const expected<_Up, _Err>&>, + is_constructible<_Tp, const expected<_Up, _Err>>, + is_convertible&, _Tp>, + is_convertible, _Tp>, + is_convertible&, _Tp>, + is_convertible, _Tp>, + is_constructible<_Unex, expected<_Up, _Err>&>, + is_constructible<_Unex, expected<_Up, _Err>>, + is_constructible<_Unex, const expected<_Up, _Err>&>, + is_constructible<_Unex, const expected<_Up, _Err>> + >; + + template + constexpr static bool __explicit_conv + = __or_v<__not_>, + __not_> + >; + + public: + using value_type = _Tp; + using error_type = _Er; + using unexpected_type = unexpected<_Er>; + + template + using rebind = expected<_Up, error_type>; + + constexpr + expected() + noexcept(is_nothrow_default_constructible_v<_Tp>) + requires is_default_constructible_v<_Tp> + : _M_val(), _M_has_value(true) + { } + + expected(const expected&) = default; + + constexpr + expected(const expected& __x) + noexcept(__and_v, + is_nothrow_copy_constructible<_Er>>) + requires is_copy_constructible_v<_Tp> && is_copy_constructible_v<_Er> + && (!is_trivially_copy_constructible_v<_Tp> + || !is_trivially_copy_constructible_v<_Er>) + : _M_invalid(), _M_has_value(__x._M_has_value) + { + if (_M_has_value) + std::construct_at(__builtin_addressof(_M_val), __x._M_val); + else + std::construct_at(__builtin_addressof(_M_unex), __x._M_unex); + } + + expected(expected&&) = default; + + constexpr + expected(expected&& __x) + noexcept(__and_v, + is_nothrow_move_constructible<_Er>>) + requires is_move_constructible_v<_Tp> && is_move_constructible_v<_Er> + && (!is_trivially_move_constructible_v<_Tp> + || !is_trivially_move_constructible_v<_Er>) + : _M_invalid(), _M_has_value(__x._M_has_value) + { + if (_M_has_value) + std::construct_at(__builtin_addressof(_M_val), + std::move(__x)._M_val); + else + std::construct_at(__builtin_addressof(_M_unex), + std::move(__x)._M_unex); + } + + template + requires is_constructible_v<_Tp, const _Up&> + && is_constructible_v<_Er, const _Gr&> + && (!__cons_from_expected<_Up, _Gr>) + constexpr explicit(__explicit_conv) + expected(const expected<_Up, _Gr>& __x) + noexcept(__and_v, + is_nothrow_constructible<_Er, const _Gr&>>) + : _M_invalid(), _M_has_value(__x._M_has_value) + { + if (_M_has_value) + std::construct_at(__builtin_addressof(_M_val), __x._M_val); + else + std::construct_at(__builtin_addressof(_M_unex), __x._M_unex); + } + + template + requires is_constructible_v<_Tp, _Up> + && is_constructible_v<_Er, _Gr> + && (!__cons_from_expected<_Up, _Gr>) + constexpr explicit(__explicit_conv<_Up, _Gr>) + expected(expected<_Up, _Gr>&& __x) + noexcept(__and_v, + is_nothrow_constructible<_Er, _Gr>>) + : _M_invalid(), _M_has_value(__x._M_has_value) + { + if (_M_has_value) + std::construct_at(__builtin_addressof(_M_val), + std::move(__x)._M_val); + else + std::construct_at(__builtin_addressof(_M_unex), + std::move(__x)._M_unex); + } + + template + requires (!is_same_v, expected>) + && (!is_same_v, in_place_t>) + && (!__expected::__is_unexpected>) + && is_constructible_v<_Tp, _Up> + constexpr explicit(!is_convertible_v<_Up, _Tp>) + expected(_Up&& __v) + noexcept(is_nothrow_constructible_v<_Tp, _Up>) + : _M_val(std::forward<_Up>(__v)), _M_has_value(true) + { } + + template + requires is_constructible_v<_Er, const _Gr&> + constexpr explicit(!is_convertible_v) + expected(const unexpected<_Gr>& __u) + noexcept(is_nothrow_constructible_v<_Er, const _Gr&>) + : _M_unex(__u.error()), _M_has_value(false) + { } + + template + requires is_constructible_v<_Er, _Gr> + constexpr explicit(!is_convertible_v<_Gr, _Er>) + expected(unexpected<_Gr>&& __u) + noexcept(is_nothrow_constructible_v<_Er, _Gr>) + : _M_unex(std::move(__u).error()), _M_has_value(false) + { } + + template + requires is_constructible_v<_Tp, _Args...> + constexpr explicit + expected(in_place_t, _Args&&... __args) + noexcept(is_nothrow_constructible_v<_Tp, _Args...>) + : _M_val(std::forward<_Args>(__args)...), _M_has_value(true) + { } + + template + requires is_constructible_v<_Tp, initializer_list<_Up>&, _Args...> + constexpr explicit + expected(in_place_t, initializer_list<_Up> __il, _Args&&... __args) + noexcept(is_nothrow_constructible_v<_Tp, initializer_list<_Up>&, + _Args...>) + : _M_val(__il, std::forward<_Args>(__args)...), _M_has_value(true) + { } + + template + requires is_constructible_v<_Er, _Args...> + constexpr explicit + expected(unexpect_t, _Args&&... __args) + noexcept(is_nothrow_constructible_v<_Er, _Args...>) + : _M_unex(std::forward<_Args>(__args)...), _M_has_value(false) + { } + + template + requires is_constructible_v<_Er, initializer_list<_Up>&, _Args...> + constexpr explicit + expected(unexpect_t, initializer_list<_Up> __il, _Args&&... __args) + noexcept(is_nothrow_constructible_v<_Er, initializer_list<_Up>&, + _Args...>) + : _M_unex(__il, std::forward<_Args>(__args)...), _M_has_value(false) + { } + + constexpr ~expected() = default; + + constexpr ~expected() + requires (!is_trivially_destructible_v<_Tp>) + || (!is_trivially_destructible_v<_Er>) + { + if (_M_has_value) + std::destroy_at(__builtin_addressof(_M_val)); + else + std::destroy_at(__builtin_addressof(_M_unex)); + } + + // assignment + + expected& operator=(const expected&) = delete; + + constexpr expected& + operator=(const expected& __x) + noexcept(__and_v, + is_nothrow_copy_constructible<_Er>, + is_nothrow_copy_assignable<_Tp>, + is_nothrow_copy_assignable<_Er>>) + requires is_copy_assignable_v<_Tp> && is_copy_constructible_v<_Tp> + && is_copy_assignable_v<_Er> && is_copy_constructible_v<_Er> + && (is_nothrow_move_constructible_v<_Tp> + || is_nothrow_move_constructible_v<_Er>) + { + if (__x._M_has_value) + this->_M_assign_val(__x._M_val); + else + this->_M_assign_unex(__x._M_unex); + return *this; + } + + constexpr expected& + operator=(expected&& __x) + noexcept(__and_v, + is_nothrow_move_constructible<_Er>, + is_nothrow_move_assignable<_Tp>, + is_nothrow_move_assignable<_Er>>) + requires is_move_assignable_v<_Tp> && is_move_constructible_v<_Tp> + && is_move_assignable_v<_Er> && is_move_constructible_v<_Er> + && (is_nothrow_move_constructible_v<_Tp> + || is_nothrow_move_constructible_v<_Er>) + { + if (__x._M_has_value) + _M_assign_val(std::move(__x._M_val)); + else + _M_assign_unex(std::move(__x._M_unex)); + return *this; + } + + template + requires (!is_same_v>) + && (!__expected::__is_unexpected>) + && is_constructible_v<_Tp, _Up> && is_assignable_v<_Tp&, _Up> + && (is_nothrow_constructible_v<_Tp, _Up> + || is_nothrow_move_constructible_v<_Tp> + || is_nothrow_move_constructible_v<_Er>) + constexpr expected& + operator=(_Up&& __v) + { + _M_assign_val(std::forward<_Up>(__v)); + return *this; + } + + template + requires is_constructible_v<_Er, const _Gr&> + && is_assignable_v<_Er&, const _Gr&> + && (is_nothrow_constructible_v<_Er, const _Gr&> + || is_nothrow_move_constructible_v<_Tp> + || is_nothrow_move_constructible_v<_Er>) + constexpr expected& + operator=(const unexpected<_Gr>& __e) + { + _M_assign_unex(__e.error()); + return *this; + } + + template + requires is_constructible_v<_Er, _Gr> + && is_assignable_v<_Er&, _Gr> + && (is_nothrow_constructible_v<_Er, _Gr> + || is_nothrow_move_constructible_v<_Tp> + || is_nothrow_move_constructible_v<_Er>) + constexpr expected& + operator=(unexpected<_Gr>&& __e) + { + _M_assign_unex(std::move(__e).error()); + return *this; + } + + // modifiers + + template + requires is_nothrow_constructible_v<_Tp, _Args...> + constexpr _Tp& + emplace(_Args&&... __args) noexcept + { + if (_M_has_value) + std::destroy_at(__builtin_addressof(_M_val)); + else + { + std::destroy_at(__builtin_addressof(_M_unex)); + _M_has_value = true; + } + std::construct_at(__builtin_addressof(_M_val), + std::forward<_Args>(__args)...); + return _M_val; + } + + template + requires is_nothrow_constructible_v<_Tp, initializer_list<_Up>&, + _Args...> + constexpr _Tp& + emplace(initializer_list<_Up> __il, _Args&&... __args) noexcept + { + if (_M_has_value) + std::destroy_at(__builtin_addressof(_M_val)); + else + { + std::destroy_at(__builtin_addressof(_M_unex)); + _M_has_value = true; + } + std::construct_at(__builtin_addressof(_M_val), + __il, std::forward<_Args>(__args)...); + return _M_val; + } + + // swap + constexpr void + swap(expected& __x) + noexcept(__and_v, + is_nothrow_move_constructible<_Er>, + is_nothrow_swappable<_Tp&>, + is_nothrow_swappable<_Er&>>) + requires is_swappable_v<_Tp> && is_swappable_v<_Er> + && is_move_constructible_v<_Tp> + && is_move_constructible_v<_Er> + && (is_nothrow_move_constructible_v<_Tp> + || is_nothrow_move_constructible_v<_Er>) + { + if (_M_has_value) + { + if (__x._M_has_value) + { + using std::swap; + swap(_M_val, __x._M_val); + } + else + this->_M_swap_val_unex(__x); + } + else + { + if (__x._M_has_value) + __x._M_swap_val_unex(*this); + else + { + using std::swap; + swap(_M_unex, __x._M_unex); + } + } + } + + // observers + + [[nodiscard]] + constexpr const _Tp* + operator->() const noexcept + { + __glibcxx_assert(_M_has_value); + return __builtin_addressof(_M_val); + } + + [[nodiscard]] + constexpr _Tp* + operator->() noexcept + { + __glibcxx_assert(_M_has_value); + return __builtin_addressof(_M_val); + } + + [[nodiscard]] + constexpr const _Tp& + operator*() const & noexcept + { + __glibcxx_assert(_M_has_value); + return _M_val; + } + + [[nodiscard]] + constexpr _Tp& + operator*() & noexcept + { + __glibcxx_assert(_M_has_value); + return _M_val; + } + + [[nodiscard]] + constexpr const _Tp&& + operator*() const && noexcept + { + __glibcxx_assert(_M_has_value); + return std::move(_M_val); + } + + [[nodiscard]] + constexpr _Tp&& + operator*() && noexcept + { + __glibcxx_assert(_M_has_value); + return std::move(_M_val); + } + + [[nodiscard]] + constexpr explicit + operator bool() const noexcept { return _M_has_value; } + + [[nodiscard]] + constexpr bool has_value() const noexcept { return _M_has_value; } + + constexpr const _Tp& + value() const & + { + if (_M_has_value) [[likely]] + return _M_val; + _GLIBCXX_THROW_OR_ABORT(bad_expected_access<_Er>(_M_unex)); + } + + constexpr _Tp& + value() & + { + if (_M_has_value) [[likely]] + return _M_val; + _GLIBCXX_THROW_OR_ABORT(bad_expected_access<_Er>(_M_unex)); + } + + constexpr const _Tp&& + value() const && + { + if (_M_has_value) [[likely]] + return std::move(_M_val); + _GLIBCXX_THROW_OR_ABORT(bad_expected_access<_Er>( + std::move(_M_unex))); + } + + constexpr _Tp&& + value() && + { + if (_M_has_value) [[likely]] + return std::move(_M_val); + _GLIBCXX_THROW_OR_ABORT(bad_expected_access<_Er>( + std::move(_M_unex))); + } + + constexpr const _Er& + error() const & noexcept + { + __glibcxx_assert(!_M_has_value); + return _M_unex; + } + + constexpr _Er& + error() & noexcept + { + __glibcxx_assert(!_M_has_value); + return _M_unex; + } + + constexpr const _Er&& + error() const && noexcept + { + __glibcxx_assert(!_M_has_value); + return std::move(_M_unex); + } + + constexpr _Er&& + error() && noexcept + { + __glibcxx_assert(!_M_has_value); + return std::move(_M_unex); + } + + template + constexpr _Tp + value_or(_Up&& __v) const & + noexcept(__and_v, + is_nothrow_convertible<_Up, _Tp>>) + { + static_assert( is_copy_constructible_v<_Tp> ); + static_assert( is_convertible_v<_Up, _Tp> ); + + if (_M_has_value) + return _M_val; + return static_cast<_Tp>(std::forward<_Up>(__v)); + } + + template + constexpr _Tp + value_or(_Up&& __v) && + noexcept(__and_v, + is_nothrow_convertible<_Up, _Tp>>) + { + static_assert( is_move_constructible_v<_Tp> ); + static_assert( is_convertible_v<_Up, _Tp> ); + + if (_M_has_value) + return std::move(_M_val); + return static_cast<_Tp>(std::forward<_Up>(__v)); + } + + // equality operators + + template + requires (!is_void_v<_Up>) + friend constexpr bool + operator==(const expected& __x, const expected<_Up, _Er2>& __y) + noexcept(noexcept(bool(*__x == *__y)) + && noexcept(bool(__x.error() == __y.error()))) + { + if (__x.has_value()) + return __y.has_value() && bool(*__x == *__y); + else + return !__y.has_value() && bool(__x.error() == __y.error()); + } + + template + friend constexpr bool + operator==(const expected& __x, const _Up& __v) + noexcept(noexcept(bool(*__x == __v))) + { return __x.has_value() && bool(*__x == __v); } + + template + friend constexpr bool + operator==(const expected& __x, const unexpected<_Er2>& __e) + noexcept(noexcept(bool(__x.error() == __e.error()))) + { return !__x.has_value() && bool(__x.error() == __e.error()); } + + friend constexpr void + swap(expected& __x, expected& __y) + noexcept(noexcept(__x.swap(__y))) + requires requires {__x.swap(__y);} + { __x.swap(__y); } + + private: + template friend class expected; + + template + constexpr void + _M_assign_val(_Vp&& __v) + { + if (_M_has_value) + _M_val = std::forward<_Vp>(__v); + else + { + __expected::__reinit(__builtin_addressof(_M_val), + __builtin_addressof(_M_unex), + std::forward<_Vp>(__v)); + _M_has_value = true; + } + } + + template + constexpr void + _M_assign_unex(_Vp&& __v) + { + if (_M_has_value) + { + __expected::__reinit(__builtin_addressof(_M_unex), + __builtin_addressof(_M_val), + std::forward<_Vp>(__v)); + _M_has_value = false; + } + else + _M_unex = std::forward<_Vp>(__v); + } + + // Swap two expected objects when only one has a value. + // Precondition: this->_M_has_value && !__rhs._M_has_value + constexpr void + _M_swap_val_unex(expected& __rhs) + noexcept(__and_v, + is_nothrow_move_constructible<_Tp>>) + { + if constexpr (is_nothrow_move_constructible_v<_Er>) + { + __expected::_Guard<_Er> __guard(__rhs._M_unex); + std::construct_at(__builtin_addressof(__rhs._M_val), + std::move(_M_val)); // might throw + __rhs._M_has_value = true; + std::destroy_at(__builtin_addressof(_M_val)); + std::construct_at(__builtin_addressof(_M_unex), + __guard.release()); + _M_has_value = false; + } + else + { + __expected::_Guard<_Tp> __guard(__rhs._M_val); + std::construct_at(__builtin_addressof(_M_unex), + std::move(__rhs._M_unex)); // might throw + _M_has_value = false; + std::destroy_at(__builtin_addressof(__rhs._M_unex)); + std::construct_at(__builtin_addressof(__rhs._M_val), + __guard.release()); + __rhs._M_has_value = true; + } + } + + union { + struct { } _M_invalid; + _Tp _M_val; + _Er _M_unex; + }; + + bool _M_has_value; + }; + + // Partial specialization for std::expected + template requires is_void_v<_Tp> + class expected<_Tp, _Er> + { + static_assert( __expected::__can_be_unexpected<_Er> ); + + template> + static constexpr bool __cons_from_expected + = __or_v&>, + is_constructible<_Unex, expected<_Up, _Err>>, + is_constructible<_Unex, const expected<_Up, _Err>&>, + is_constructible<_Unex, const expected<_Up, _Err>> + >; + + public: + using value_type = _Tp; + using error_type = _Er; + using unexpected_type = unexpected<_Er>; + + template + using rebind = expected<_Up, error_type>; + + constexpr + expected() noexcept + : _M_void(), _M_has_value(true) + { } + + expected(const expected&) = default; + + constexpr + expected(const expected& __x) + noexcept(is_nothrow_copy_constructible_v<_Er>) + requires is_copy_constructible_v<_Er> + && (!is_trivially_copy_constructible_v<_Er>) + : _M_void(), _M_has_value(__x._M_has_value) + { + if (!_M_has_value) + std::construct_at(__builtin_addressof(_M_unex), __x._M_unex); + } + + expected(expected&&) = default; + + constexpr + expected(expected&& __x) + noexcept(is_nothrow_move_constructible_v<_Er>) + requires is_move_constructible_v<_Er> + && (!is_trivially_move_constructible_v<_Er>) + : _M_void(), _M_has_value(__x._M_has_value) + { + if (!_M_has_value) + std::construct_at(__builtin_addressof(_M_unex), + std::move(__x)._M_unex); + } + + template + requires is_void_v<_Up> + && is_constructible_v<_Er, const _Gr&> + && (!__cons_from_expected<_Up, _Gr>) + constexpr explicit(!is_convertible_v) + expected(const expected<_Up, _Gr>& __x) + noexcept(is_nothrow_constructible_v<_Er, const _Gr&>) + : _M_void(), _M_has_value(__x._M_has_value) + { + if (!_M_has_value) + std::construct_at(__builtin_addressof(_M_unex), __x._M_unex); + } + + template + requires is_void_v<_Tp> + && is_constructible_v<_Er, const _Gr&> + && (!__cons_from_expected<_Up, _Gr>) + constexpr explicit(!is_convertible_v<_Gr, _Er>) + expected(expected<_Up, _Gr>&& __x) + noexcept(is_nothrow_constructible_v<_Er, _Gr>) + : _M_void(), _M_has_value(__x._M_has_value) + { + if (!_M_has_value) + std::construct_at(__builtin_addressof(_M_unex), + std::move(__x)._M_unex); + } + + template + requires is_constructible_v<_Er, const _Gr&> + constexpr explicit(!is_convertible_v) + expected(const unexpected<_Gr>& __u) + noexcept(is_nothrow_constructible_v<_Er, const _Gr&>) + : _M_unex(__u.error()), _M_has_value(false) + { } + + template + requires is_constructible_v<_Er, _Gr> + constexpr explicit(!is_convertible_v<_Gr, _Er>) + expected(unexpected<_Gr>&& __u) + noexcept(is_nothrow_constructible_v<_Er, _Gr>) + : _M_unex(std::move(__u).error()), _M_has_value(false) + { } + + template + constexpr explicit + expected(in_place_t) noexcept + : expected() + { } + + template + requires is_constructible_v<_Er, _Args...> + constexpr explicit + expected(unexpect_t, _Args&&... __args) + noexcept(is_nothrow_constructible_v<_Er, _Args...>) + : _M_unex(std::forward<_Args>(__args)...), _M_has_value(false) + { } + + template + requires is_constructible_v<_Er, initializer_list<_Up>&, _Args...> + constexpr explicit + expected(unexpect_t, initializer_list<_Up> __il, _Args&&... __args) + noexcept(is_nothrow_constructible_v<_Er, initializer_list<_Up>&, + _Args...>) + : _M_unex(__il, std::forward<_Args>(__args)...), _M_has_value(false) + { } + + constexpr ~expected() = default; + + constexpr ~expected() requires (!is_trivially_destructible_v<_Er>) + { + if (!_M_has_value) + std::destroy_at(__builtin_addressof(_M_unex)); + } + + // assignment + + expected& operator=(const expected&) = delete; + + constexpr expected& + operator=(const expected& __x) + noexcept(__and_v, + is_nothrow_copy_assignable<_Er>>) + requires is_copy_constructible_v<_Er> + && is_copy_assignable_v<_Er> + { + if (__x._M_has_value) + emplace(); + else + _M_assign_unex(__x._M_unex); + return *this; + } + + constexpr expected& + operator=(expected&& __x) + noexcept(__and_v, + is_nothrow_move_assignable<_Er>>) + requires is_move_constructible_v<_Er> + && is_move_assignable_v<_Er> + { + if (__x._M_has_value) + emplace(); + else + _M_assign_unex(std::move(__x._M_unex)); + return *this; + } + + template + requires is_constructible_v<_Er, const _Gr&> + && is_assignable_v<_Er&, const _Gr&> + constexpr expected& + operator=(const unexpected<_Gr>& __e) + { + _M_assign_unex(__e.error()); + return *this; + } + + template + requires is_constructible_v<_Er, _Gr> + && is_assignable_v<_Er&, _Gr> + constexpr expected& + operator=(unexpected<_Gr>&& __e) + { + _M_assign_unex(std::move(__e.error())); + return *this; + } + + // modifiers + + constexpr void + emplace() noexcept + { + if (!_M_has_value) + { + std::destroy_at(__builtin_addressof(_M_unex)); + _M_has_value = true; + } + } + + // swap + constexpr void + swap(expected& __x) + noexcept(__and_v, + is_nothrow_move_constructible<_Er>>) + requires is_swappable_v<_Er> && is_move_constructible_v<_Er> + { + if (_M_has_value) + { + if (!__x._M_has_value) + { + std::construct_at(__builtin_addressof(_M_unex), + std::move(__x._M_unex)); // might throw + std::destroy_at(__builtin_addressof(__x._M_unex)); + __x._M_has_value = true; + } + } + else + { + if (__x._M_has_value) + { + std::construct_at(__builtin_addressof(__x._M_unex), + std::move(_M_unex)); // might throw + std::destroy_at(__builtin_addressof(_M_unex)); + _M_has_value = true; + } + else + { + using std::swap; + swap(_M_unex, __x._M_unex); + } + } + } + + // observers + + [[nodiscard]] + constexpr explicit + operator bool() const noexcept { return _M_has_value; } + + [[nodiscard]] + constexpr bool has_value() const noexcept { return _M_has_value; } + + constexpr void + operator*() const noexcept { __glibcxx_assert(_M_has_value); } + + constexpr void + value() const& + { + if (_M_has_value) [[likely]] + return; + _GLIBCXX_THROW_OR_ABORT(bad_expected_access<_Er>(_M_unex)); + } + + constexpr void + value() && + { + if (_M_has_value) [[likely]] + return; + _GLIBCXX_THROW_OR_ABORT(bad_expected_access<_Er>(std::move(_M_unex))); + } + + constexpr const _Er& + error() const & noexcept + { + __glibcxx_assert(!_M_has_value); + return _M_unex; + } + + constexpr _Er& + error() & noexcept + { + __glibcxx_assert(!_M_has_value); + return _M_unex; + } + + constexpr const _Er&& + error() const && noexcept + { + __glibcxx_assert(!_M_has_value); + return std::move(_M_unex); + } + + constexpr _Er&& + error() && noexcept + { + __glibcxx_assert(!_M_has_value); + return std::move(_M_unex); + } + + // equality operators + + template + requires is_void_v<_Up> + friend constexpr bool + operator==(const expected& __x, const expected<_Up, _Er2>& __y) + noexcept(noexcept(bool(__x.error() == __y.error()))) + { + if (__x.has_value()) + return __y.has_value(); + else + return !__y.has_value() && bool(__x.error() == __y.error()); + } + + template + friend constexpr bool + operator==(const expected& __x, const unexpected<_Er2>& __e) + noexcept(noexcept(bool(__x.error() == __e.error()))) + { return !__x.has_value() && bool(__x.error() == __e.error()); } + + friend constexpr void + swap(expected& __x, expected& __y) + noexcept(noexcept(__x.swap(__y))) + requires requires { __x.swap(__y); } + { __x.swap(__y); } + + private: + template friend class expected; + + template + constexpr void + _M_assign_unex(_Vp&& __v) + { + if (_M_has_value) + { + std::construct_at(__builtin_addressof(_M_unex), + std::forward<_Vp>(__v)); + _M_has_value = false; + } + else + _M_unex = std::forward<_Vp>(__v); + } + + + union { + struct { } _M_void; + _Er _M_unex; + }; + + bool _M_has_value; + }; + /// @} + +_GLIBCXX_END_NAMESPACE_VERSION +} // namespace std + +#endif // C++23 +#endif // _GLIBCXX_EXPECTED diff --git a/libstdc++-v3/include/std/version b/libstdc++-v3/include/std/version index 535f095108a..7dbac23f22d 100644 --- a/libstdc++-v3/include/std/version +++ b/libstdc++-v3/include/std/version @@ -306,6 +306,7 @@ #if _GLIBCXX_HOSTED #define __cpp_lib_adaptor_iterator_pair_constructor 202106L +#define __cpp_lib_expected 202202L #define __cpp_lib_invoke_r 202106L #define __cpp_lib_ios_noreplace 202200L #if __cpp_lib_concepts diff --git a/libstdc++-v3/libsupc++/exception b/libstdc++-v3/libsupc++/exception index 43f1cf71262..ae2b0dd7f78 100644 --- a/libstdc++-v3/libsupc++/exception +++ b/libstdc++-v3/libsupc++/exception @@ -79,7 +79,7 @@ namespace std * abandoned for any reason. It can also be called by the user. */ void terminate() _GLIBCXX_USE_NOEXCEPT __attribute__ ((__noreturn__)); -#if __cplusplus < 201703L || _GLIBCXX_USE_DEPRECATED +#if __cplusplus < 201703L || (__cplusplus <= 202002L && _GLIBCXX_USE_DEPRECATED) /// If you write a replacement %unexpected handler, it must be of this type. typedef void (*_GLIBCXX11_DEPRECATED unexpected_handler) (); diff --git a/libstdc++-v3/testsuite/20_util/expected/assign.cc b/libstdc++-v3/testsuite/20_util/expected/assign.cc new file mode 100644 index 00000000000..bbf5b900f4c --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/expected/assign.cc @@ -0,0 +1,321 @@ +// { dg-options "-std=gnu++23" } +// { dg-do run { target c++23 } } + +#include +#include +#include + +int dtor_count; +constexpr void reset_dtor_count() +{ + if (!std::is_constant_evaluated()) + dtor_count = 0; +} +constexpr void inc_dtor_count() +{ + if (!std::is_constant_evaluated()) + ++dtor_count; +} +constexpr bool check_dtor_count(int c) +{ + if (std::is_constant_evaluated()) + return true; + return dtor_count == c; +} + +struct X +{ + constexpr X(int i, int j = 0) noexcept : n(i+j) { } + constexpr X(std::initializer_list l, void*) noexcept : n(l.size()) { } + + constexpr X(const X&) = default; + constexpr X(X&& x) noexcept : n(x.n) { x.n = -1; } + + constexpr X& operator=(const X&) = default; + constexpr X& operator=(X&& x) noexcept { n = x.n; x.n = -1; return *this; } + + constexpr ~X() + { + inc_dtor_count(); + } + + constexpr bool operator==(const X&) const = default; + constexpr bool operator==(int i) const { return n == i; } + + int n; +}; + +constexpr bool +test_copy(bool = true) +{ + reset_dtor_count(); + + std::expected e1(1), e2(2), e3(std::unexpect, 3); + + e1 = e1; + e1 = e2; // T = T + VERIFY( e1.value() == e2.value() ); + e1 = e3; // T = E + VERIFY( ! e1.has_value() ); + VERIFY( e1.error() == e3.error() ); + e1 = e3; // E = E + VERIFY( ! e1.has_value() ); + VERIFY( e1.error() == e3.error() ); + e1 = e2; // E = T + VERIFY( e1.value() == e2.value() ); + + e1 = std::move(e1); + e1 = std::move(e2); // T = T + VERIFY( e1.value() == e2.value() ); + e1 = std::move(e3); // T = E + VERIFY( ! e1.has_value() ); + VERIFY( e1.error() == e3.error() ); + e1 = std::move(e3); // E = E + VERIFY( ! e1.has_value() ); + VERIFY( e1.error() == e3.error() ); + e1 = std::move(e2); // E = T + VERIFY( e1.value() == e2.value() ); + + std::expected x1(1), x2(2), x3(std::unexpect, 3); + + x1 = x1; + + x1 = x2; // T = T + VERIFY( check_dtor_count(0) ); + VERIFY( x1.value() == x2.value() ); + x1 = x3; // T = E + VERIFY( check_dtor_count(1) ); + VERIFY( ! x1.has_value() ); + x1 = x3; // E = E + VERIFY( check_dtor_count(1) ); + VERIFY( ! x1.has_value() ); + x1 = x2; // E = T + VERIFY( check_dtor_count(2) ); + VERIFY( x1.value() == x2.value() ); + + reset_dtor_count(); + + x1 = std::move(x1); + VERIFY( x1.value() == -1 ); + + x1 = std::move(x2); // T = T + VERIFY( check_dtor_count(0) ); + VERIFY( x1.value() == 2 ); + VERIFY( x2.value() == -1 ); + x1 = std::move(x3); // T = E + VERIFY( check_dtor_count(1) ); + VERIFY( ! x1.has_value() ); + VERIFY( x1.error() == 3 ); + VERIFY( x3.error() == -1 ); + x3.error().n = 33; + x1 = std::move(x3); // E = E + VERIFY( check_dtor_count(1) ); + VERIFY( ! x1.has_value() ); + VERIFY( x1.error() == 33 ); + VERIFY( x3.error() == -1 ); + x2.value().n = 22; + x1 = std::move(x2); // E = T + VERIFY( check_dtor_count(2) ); + VERIFY( x1.value() == 22 ); + VERIFY( x2.value() == -1 ); + + std::expected ev1, ev2, ev3(std::unexpect, 3); + + ev1 = ev2; // T = T + VERIFY( ev1.has_value() ); + ev1 = ev3; // T = E + VERIFY( ! ev1.has_value() ); + VERIFY( ev1.error() == ev3.error() ); + ev1 = ev3; // E = E + VERIFY( ! ev1.has_value() ); + VERIFY( ev1.error() == ev3.error() ); + ev1 = ev2; // E = T + VERIFY( ev1.has_value() ); + + reset_dtor_count(); + std::expected xv1, xv2, xv3(std::unexpect, 3); + + xv1 = std::move(xv2); // T = T + VERIFY( check_dtor_count(0) ); + VERIFY( xv1.has_value() ); + xv1 = std::move(xv3); // T = E + VERIFY( check_dtor_count(0) ); + VERIFY( ! xv1.has_value() ); + VERIFY( xv1.error() == 3 ); + VERIFY( xv3.error() == -1 ); + xv3.error().n = 33; + xv1 = std::move(xv3); // E = E + VERIFY( check_dtor_count(0) ); + VERIFY( xv1.error() == 33 ); + VERIFY( xv3.error() == -1 ); + xv1 = std::move(xv2); // E = T + VERIFY( check_dtor_count(1) ); + VERIFY( xv1.has_value() ); + + return true; +} + +constexpr bool +test_converting(bool = true) +{ + std::expected e1(1); + std::expected e2(2U), e3(std::unexpect, 3L); + e1 = e2; + VERIFY( e1.value() == e2.value() ); + e1 = e3; + VERIFY( ! e1.has_value() ); + VERIFY( e1.error() == e3.error() ); + e1 = e2; + VERIFY( e1.value() == e2.value() ); + + e1 = std::move(e3); + VERIFY( ! e1.has_value() ); + VERIFY( e1.error() == e3.error() ); + e1 = std::move(e2); + VERIFY( e1.value() == e2.value() ); + + std::expected ev4; + std::expected ev5(std::unexpect, 5); + ev4 = ev5; + VERIFY( ! ev4.has_value() ); + VERIFY( ev4.error() == 5 ); + ev4 = std::expected(); + VERIFY( ev4.has_value() ); + ev4 = std::move(ev5); + VERIFY( ! ev4.has_value() ); + VERIFY( ev4.error() == 5 ); + + return true; +} + +constexpr bool +test_unexpected(bool = true) +{ + reset_dtor_count(); + + std::expected e1(0); + + e1 = std::unexpected(5); + VERIFY( ! e1.has_value() ); + VERIFY( e1.error() == 5 ); + VERIFY( check_dtor_count(1) ); + + e1 = std::unexpected(6); + VERIFY( check_dtor_count(1) ); + + std::expected e2; + + std::unexpected x(std::in_place, 1, 2); + e2 = x; + VERIFY( check_dtor_count(1) ); + + e2 = 1; + VERIFY( e2.value() == 1 ); + VERIFY( check_dtor_count(2) ); + + return true; +} + +constexpr bool +test_emplace(bool = true) +{ + reset_dtor_count(); + + std::expected e1(1); + e1.emplace(2); + VERIFY( e1.value() == 2 ); + + std::expected ev2; + ev2.emplace(); + VERIFY( ev2.has_value() ); + + std::expected e3(std::in_place, 0, 0); + + e3.emplace({1,2,3}, nullptr); + VERIFY( e3.value() == 3 ); + VERIFY( check_dtor_count(1) ); + + e3.emplace(2, 2); + VERIFY( e3.value() == 4 ); + VERIFY( check_dtor_count(2) ); + + std::expected ev4(std::unexpect, 4); + + ev4.emplace(5); + VERIFY( ev4.value() == 5 ); + VERIFY( check_dtor_count(3) ); + + ev4.emplace(6); + VERIFY( ev4.value() == 6 ); + VERIFY( check_dtor_count(3) ); + + return true; +} + +void +test_exception_safety() +{ + struct CopyThrows + { + CopyThrows(int i) noexcept : x(i) { } + CopyThrows(const CopyThrows&) { throw 1; } + CopyThrows(CopyThrows&&) = default; + CopyThrows& operator=(const CopyThrows&) = default; + CopyThrows& operator=(CopyThrows&&) = default; + int x; + + bool operator==(int i) const { return x == i; } + }; + + struct MoveThrows + { + MoveThrows(int i) noexcept : x(i) { } + MoveThrows(const MoveThrows&) = default; + MoveThrows(MoveThrows&&) { throw 1L; } + MoveThrows& operator=(const MoveThrows&) = default; + MoveThrows& operator=(MoveThrows&&) = default; + int x; + + bool operator==(int i) const { return x == i; } + }; + + std::expected c(std::unexpect, 1); + + // operator=(U&&) + try { + CopyThrows x(2); + c = x; + VERIFY( false ); + } catch (int) { + VERIFY( ! c.has_value() ); + VERIFY( c.error() == 1 ); + } + + c = CopyThrows(2); + + try { + c = std::unexpected(3); + VERIFY( false ); + } catch (long) { + VERIFY( c.value() == 2 ); + } +} + +int main(int argc, char**) +{ + bool non_constant = argc == 1; // force non-constant evaluation + + static_assert( test_copy() ); + test_copy(non_constant); + static_assert( test_converting() ); + test_converting(non_constant); + static_assert( test_unexpected() ); + test_unexpected(non_constant); + static_assert( test_emplace() ); + test_emplace(non_constant); + + test_exception_safety(); + + // Ensure the non-constexpr tests actually ran: + VERIFY( dtor_count != 0 ); +} diff --git a/libstdc++-v3/testsuite/20_util/expected/cons.cc b/libstdc++-v3/testsuite/20_util/expected/cons.cc new file mode 100644 index 00000000000..1fe5b7bf4d1 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/expected/cons.cc @@ -0,0 +1,175 @@ +// { dg-options "-std=gnu++23" } +// { dg-do run { target c++23 } } + +#include +#include + +constexpr bool +test_default() +{ + std::expected e; + VERIFY( e.has_value() ); + VERIFY( *e == 0 ); + + std::expected ev; + VERIFY( ev.has_value() ); + VERIFY( (ev.value(), true) ); + + return true; +} + +constexpr bool +test_val() +{ + std::expected e1(1); + VERIFY( e1.has_value() ); + VERIFY( *e1 == 1 ); + + std::expected e2(std::in_place, 2); + VERIFY( e2.has_value() ); + VERIFY( *e2 == 2 ); + + struct X + { + constexpr X(std::initializer_list l, void*) : n(l.size()) { } + int n; + }; + + std::expected e3(X{{1, 2, 3}, nullptr}); + VERIFY( e3.has_value() ); + VERIFY( e3->n == 3 ); + + std::expected e4(std::in_place, {1, 2, 3, 4}, nullptr); + VERIFY( e4.has_value() ); + VERIFY( e4->n == 4 ); + + std::expected ev(std::in_place); + VERIFY( ev.has_value() ); + VERIFY( (ev.value(), true) ); + + return true; +} + +constexpr bool +test_err() +{ + std::expected e1(std::unexpected(1)); + VERIFY( ! e1.has_value() ); + VERIFY( e1.error() == 1 ); + + const std::unexpected u2(2); + std::expected e2(u2); + VERIFY( ! e2.has_value() ); + VERIFY( e2.error() == 2 ); + + std::expected e3(std::unexpect, 3); + VERIFY( ! e3.has_value() ); + VERIFY( e3.error() == 3 ); + + struct X + { + constexpr X(int i, int j) : n(i+j) { } + constexpr X(std::initializer_list l, void*) : n(l.size()) { } + int n; + }; + + std::expected e4(std::unexpect, 1, 3); + VERIFY( ! e4.has_value() ); + VERIFY( e4.error().n == 4 ); + + std::expected e5(std::unexpect, {1, 2, 3, 4, 5}, nullptr); + VERIFY( ! e5.has_value() ); + VERIFY( e5.error().n == 5 ); + + std::expected ev1(std::unexpected(1)); + VERIFY( ! ev1.has_value() ); + VERIFY( ev1.error() == 1 ); + + std::expected ev2(u2); + VERIFY( ! ev2.has_value() ); + VERIFY( ev2.error() == 2 ); + + std::expected ev3(std::unexpect, 3); + VERIFY( ! ev3.has_value() ); + VERIFY( ev3.error() == 3 ); + + std::expected ev4(std::unexpect, 1, 3); + VERIFY( ! ev4.has_value() ); + VERIFY( ev4.error().n == 4 ); + + std::expected ev5(std::unexpect, {1, 2, 3, 4, 5}, nullptr); + VERIFY( ! ev5.has_value() ); + VERIFY( ev5.error().n == 5 ); + + return true; +} + +constexpr bool +test_copy() +{ + std::expected e1(1); + std::expected e2(e1); + VERIFY( e2.value() == 1 ); + std::expected e3(std::move(e2)); + VERIFY( e2.value() == 1 ); + VERIFY( e3.value() == 1 ); + std::expected e4(e1); + VERIFY( e4.value() == 1 ); + std::expected e5(std::move(e4)); + VERIFY( e4.value() == 1 ); + VERIFY( e5.value() == 1 ); + + std::expected u1(std::unexpect, 2); + std::expected u2(u1); + VERIFY( ! u2.has_value() ); + VERIFY( u2.error() == 2 ); + std::expected u3(std::move(u2)); + VERIFY( ! u3.has_value() ); + VERIFY( u3.error() == 2 ); + std::expected u4(u1); + VERIFY( ! u4.has_value() ); + VERIFY( u4.error() == 2 ); + std::expected u5(std::move(u4)); + VERIFY( ! u5.has_value() ); + VERIFY( u5.error() == 2 ); + + std::expected ev1; + std::expected ev2(ev1); + VERIFY( ev2.has_value() ); + std::expected ev3(std::move(ev2)); + VERIFY( ev2.has_value() ); + VERIFY( ev3.has_value() ); + std::expected ev4(ev1); + VERIFY( ev4.has_value() ); + std::expected ev5(std::move(ev4)); + VERIFY( ev4.has_value() ); + VERIFY( ev5.has_value() ); + + std::expected uv1(std::unexpect, 2); + std::expected uv2(uv1); + VERIFY( ! uv2.has_value() ); + VERIFY( uv2.error() == 2 ); + std::expected uv3(std::move(uv2)); + VERIFY( ! uv3.has_value() ); + VERIFY( uv3.error() == 2 ); + std::expected uv4(uv1); + VERIFY( ! uv4.has_value() ); + VERIFY( uv4.error() == 2 ); + std::expected uv5(std::move(uv4)); + VERIFY( ! uv5.has_value() ); + VERIFY( uv5.error() == 2 ); + + return true; +} + +int main() +{ + test_default(); + static_assert( test_default() ); + test_val(); + static_assert( test_val() ); + test_err(); + static_assert( test_err() ); + test_copy(); + static_assert( test_copy() ); +} diff --git a/libstdc++-v3/testsuite/20_util/expected/illformed_neg.cc b/libstdc++-v3/testsuite/20_util/expected/illformed_neg.cc new file mode 100644 index 00000000000..921306bc667 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/expected/illformed_neg.cc @@ -0,0 +1,67 @@ +// { dg-options "-std=gnu++23" } +// { dg-do compile { target c++23 } } + +#include + +void +test_unexpected() +{ + int i[2]{}; + + // std::unexpected is ill-formed if E is a non-object type, + + std::unexpected ref(i[0]); // { dg-error "here" } + std::unexpected func(test_unexpected); // { dg-error "here" } + // { dg-error "no matching function for call to" "" { target *-*-* } 0 } + // { dg-error "invalidly declared function type" "" { target *-*-* } 0 } + + // an array type, + std::unexpected array(i); // { dg-error "here" } + + // a specialization of std::unexpected, + std::unexpected u(1); + std::unexpected> nested(u); // { dg-error "here" } + // + // or a cv-qualified type. + std::unexpected c_int(1); // { dg-error "here" } + std::unexpected v_int(1); // { dg-error "here" } +} + +void +test_expected_value() +{ + // std::expected is ill-formed if T is a reference type, + std::expected ref(std::unexpect); // { dg-error "here" } + // { dg-error "reference type" "" { target *-*-* } 0 } + + // a function type, + std::expected func(std::unexpect); // { dg-error "here" } + // { dg-error "returning a function" "" { target *-*-* } 0 } + // + // possibly cv-qualified types in_place_t, + std::expected tag(std::unexpect); // { dg-error "here" } + std::expected ctag(std::unexpect); // { dg-error "here" } + // unexpect_t, + std::expected utag(std::unexpect); // { dg-error "here" } + std::expected cutag(std::unexpect); // { dg-error "here" } + // or a specialization of unexpected. + std::expected, int> unex(std::in_place, 1); // { dg-error "here" } + std::expected, int> cunex(std::in_place, 1); // { dg-error "here" } +} + +void +test_expected_error() +{ + + // std::expected is ill-formed if std::unexpected would be + // ill-formed. Test the same types as in test_unexpected(). + + std::expected ref; // { dg-error "here" } + std::expected func; // { dg-error "here" } + std::expected array; // { dg-error "here" } + std::expected> nested; // { dg-error "here" } + std::expected c_int; // { dg-error "here" } + std::expected v_int; // { dg-error "here" } +} + +// { dg-prune-output "static assertion failed" } diff --git a/libstdc++-v3/testsuite/20_util/expected/observers.cc b/libstdc++-v3/testsuite/20_util/expected/observers.cc new file mode 100644 index 00000000000..e76ca378026 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/expected/observers.cc @@ -0,0 +1,209 @@ +// { dg-options "-std=gnu++23" } +// { dg-do run { target c++23 } } + +#include +#include +#include + +struct X +{ + constexpr int f() & { return 1; } + constexpr int f() const & { return 2; } + constexpr int f() && { return 3; } + constexpr int f() const && { return 4; } +}; + +constexpr bool +test_arrow() +{ + std::expected e1; + VERIFY( e1->f() == 1 ); + const auto& e2 = e1; + VERIFY( e2->f() == 2 ); + + return true; +} + +constexpr bool +test_star() +{ + std::expected e1; + VERIFY( (*e1).f() == 1 ); + VERIFY( std::move(*e1).f() == 3 ); + const auto& e2 = e1; + VERIFY( (*e2).f() == 2 ); + VERIFY( std::move(*e2).f() == 4 ); + + std::expected v; + *v; + + return true; +} + +constexpr bool +test_has_value() +{ + std::expected e; + VERIFY( e.has_value() ); + VERIFY( e ); + e = std::unexpected(1); + VERIFY( ! e.has_value() ); + VERIFY( ! e ); + + std::expected v; + VERIFY( v.has_value() ); + VERIFY( v ); + v = std::unexpected(1); + VERIFY( ! v.has_value() ); + VERIFY( ! v ); + + return true; +} + +constexpr bool +test_value() +{ + std::expected e1; + + VERIFY( e1.value().f() == 1 ); + VERIFY( std::move(e1).value().f() == 3 ); + const auto& e2 = e1; + VERIFY( e2.value().f() == 2 ); + VERIFY( std::move(e2).value().f() == 4 ); + + std::expected v1; + v1.value(); + std::move(v1).value(); + + return true; +} + +void +test_value_throw() +{ + std::expected e1 = std::unexpected(9); + + try { + e1.value(); + VERIFY( false ); + } catch (const std::bad_expected_access& e) { + VERIFY( e.error() == 9 ); + } + try { + std::move(e1).value(); + VERIFY( false ); + } catch (const std::bad_expected_access& e) { + VERIFY( e.error() == 9 ); + } + + const auto& e2 = e1; + try { + e2.value(); + VERIFY( false ); + } catch (const std::bad_expected_access& e) { + VERIFY( e.error() == 9 ); + } + try { + std::move(e2).value(); + VERIFY( false ); + } catch (const std::bad_expected_access& e) { + VERIFY( e.error() == 9 ); + } + + std::expected v1 = std::unexpected(8); + try { + v1.value(); + VERIFY( false ); + } catch (const std::bad_expected_access& e) { + VERIFY( e.error() == 8 ); + } + try { + std::move(v1).value(); + VERIFY( false ); + } catch (const std::bad_expected_access& e) { + VERIFY( e.error() == 8 ); + } +} + +constexpr bool +test_error() +{ + std::expected e1(std::unexpect); + + VERIFY( e1.error().f() == 1 ); + VERIFY( std::move(e1).error().f() == 3 ); + const auto& e2 = e1; + VERIFY( e2.error().f() == 2 ); + VERIFY( std::move(e2).error().f() == 4 ); + + std::expected v1(std::unexpect); + + VERIFY( v1.error().f() == 1 ); + VERIFY( std::move(v1).error().f() == 3 ); + const auto& v2 = v1; + VERIFY( v2.error().f() == 2 ); + VERIFY( std::move(v2).error().f() == 4 ); + + return true; +} + +constexpr bool +test_value_or() +{ + struct Movable + { + constexpr Movable(int i) : x(i) { } + constexpr Movable(const Movable&) = default; + constexpr Movable(Movable&& m) : x(m.x) { m.x = -1; } + int x; + + constexpr bool operator==(int i) const { return x == i; } + }; + + std::expected e1(1); + + Movable m2(2); + VERIFY( e1.value_or(2) == 1 ); + VERIFY( e1.value_or(m2) == 1 ); + VERIFY( e1.value_or(std::move(m2)) == 1 ); + VERIFY( m2 == 2 ); + + VERIFY( std::move(e1).value_or(m2) == 1 ); + VERIFY( *e1 == -1 ); // moved + VERIFY( m2 == 2 ); + + e1 = std::unexpected(3); + VERIFY( e1.value_or(m2) == 2 ); + VERIFY( m2 == 2 ); + VERIFY( std::move(e1).value_or(m2) == 2 ); + VERIFY( m2 == 2 ); + + VERIFY( e1.value_or(std::move(m2)) == 2 ); + VERIFY( m2 == -1 ); + + m2.x = 4; + VERIFY( std::move(e1).value_or(std::move(m2)) == 4 ); + VERIFY( m2 == -1 ); + + VERIFY( e1.value_or(5) == 5 ); + VERIFY( std::move(e1).value_or(6) == 6 ); + + return true; +} + +int main() +{ + static_assert( test_arrow() ); + test_arrow(); + static_assert( test_star() ); + test_star(); + static_assert( test_has_value() ); + test_has_value(); + static_assert( test_value() ); + test_value(); + test_value_throw(); + static_assert( test_error() ); + test_error(); + static_assert( test_value_or() ); + test_value_or(); +} diff --git a/libstdc++-v3/testsuite/20_util/expected/requirements.cc b/libstdc++-v3/testsuite/20_util/expected/requirements.cc new file mode 100644 index 00000000000..485aa338679 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/expected/requirements.cc @@ -0,0 +1,129 @@ +// { dg-options "-std=gnu++23" } +// { dg-do compile { target c++23 } } + +#include +#include + +// Default construction + +template + constexpr bool default_constructible + = std::is_default_constructible_v>; + +struct A { A(int); }; + +static_assert( default_constructible< int, int > ); +static_assert( default_constructible< A, int > == false ); +static_assert( default_constructible< int, A > ); +static_assert( default_constructible< A, A > == false ); +static_assert( default_constructible< int, A > ); +static_assert( default_constructible< void, int > ); + +// Destruction + +template + constexpr bool trivially_destructible + = std::is_trivially_destructible_v>; + +struct B { ~B(); }; + +static_assert( trivially_destructible< int, int > ); +static_assert( trivially_destructible< B, int > == false ); +static_assert( trivially_destructible< int, B > == false ); +static_assert( trivially_destructible< B, B > == false ); +static_assert( trivially_destructible< void, int > ); +static_assert( trivially_destructible< void, B > == false ); + +enum Result { No, Yes, NoThrow, Trivial }; + +// Copy construction + +template + constexpr Result copy_constructible + = std::is_trivially_copy_constructible_v> ? Trivial + : std::is_copy_constructible_v> ? Yes + : No; + +struct C { C(const C&); }; +struct D { D(D&&); }; + +static_assert( copy_constructible< int, int > == Trivial ); +static_assert( copy_constructible< C, C > == Yes ); +static_assert( copy_constructible< C, int > == Yes ); +static_assert( copy_constructible< int, C > == Yes ); +static_assert( copy_constructible< int, D > == No ); +static_assert( copy_constructible< D, int > == No ); +static_assert( copy_constructible< D, D > == No ); +static_assert( copy_constructible< void, int > == Trivial ); +static_assert( copy_constructible< void, C > == Yes ); +static_assert( copy_constructible< void, D > == No ); + +// Move construction + +template + constexpr Result move_constructible + = std::is_trivially_move_constructible_v> ? Trivial + : std::is_nothrow_move_constructible_v> ? NoThrow + : std::is_move_constructible_v> ? Yes + : No; + +struct E { E(E&&) noexcept; }; + +static_assert( move_constructible< int, int > == Trivial ); +static_assert( move_constructible< C, C > == Yes ); +static_assert( move_constructible< C, int > == Yes ); +static_assert( move_constructible< int, C > == Yes ); +static_assert( move_constructible< D, D > == Yes ); +static_assert( move_constructible< D, int > == Yes ); +static_assert( move_constructible< int, D > == Yes ); +static_assert( move_constructible< E, E > == NoThrow ); +static_assert( move_constructible< E, int > == NoThrow ); +static_assert( move_constructible< int, E > == NoThrow ); +static_assert( move_constructible< void, int > == Trivial ); +static_assert( move_constructible< void, C > == Yes ); +static_assert( move_constructible< void, D > == Yes ); +static_assert( move_constructible< void, E > == NoThrow ); + +// Copy assignment + +template + constexpr bool copy_assignable + = std::is_copy_assignable_v>; + +struct F { F(F&&); F& operator=(const F&); }; // not copy-constructible +struct G { G(const G&); G(G&&); G& operator=(const G&); }; // throwing move + +static_assert( copy_assignable< int, int > ); +static_assert( copy_assignable< F, int > == false ); +static_assert( copy_assignable< int, F > == false ); +static_assert( copy_assignable< F, F > == false ); +static_assert( copy_assignable< G, int > ); +static_assert( copy_assignable< int, G > ); +static_assert( copy_assignable< G, G > == false ); +static_assert( copy_assignable< void, int > ); +static_assert( copy_assignable< void, F > == false ); +static_assert( copy_assignable< void, G > ); + +// Move assignment + +template + constexpr bool move_assignable + = std::is_move_assignable_v>; + +static_assert( move_assignable< int, int > ); +static_assert( move_assignable< F, int > ); +static_assert( move_assignable< int, F > ); +static_assert( move_assignable< F, F > == false ); +static_assert( move_assignable< G, int > ); +static_assert( move_assignable< int, G > ); +static_assert( move_assignable< G, G > == false ); +static_assert( move_assignable< void, int > ); +static_assert( move_assignable< void, F > ); +static_assert( move_assignable< void, G > ); + +// QoI properties +static_assert( sizeof(std::expected) == 2 ); +static_assert( sizeof(std::expected) == 2 ); +static_assert( sizeof(std::expected) == 2 * __alignof(void*) ); +static_assert( alignof(std::expected) == 1 ); +static_assert( alignof(std::expected) == alignof(void*) ); diff --git a/libstdc++-v3/testsuite/20_util/expected/swap.cc b/libstdc++-v3/testsuite/20_util/expected/swap.cc new file mode 100644 index 00000000000..1b3b8c5f4e8 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/expected/swap.cc @@ -0,0 +1,57 @@ +// { dg-options "-std=gnu++23" } +// { dg-do run { target c++23 } } + +#include +#include + +constexpr bool +test_swap() +{ + std::expected e1(1), e2(2); + std::expected e3(std::unexpect, 3), e4(std::unexpect, 4); + + swap(e1, e2); + VERIFY( e1.value() == 2 ); + VERIFY( e2.value() == 1 ); + swap(e1, e3); + VERIFY( ! e1.has_value() ); + VERIFY( e1.error() == 3 ); + VERIFY( e3.value() == 2 ); + swap(e1, e3); + VERIFY( ! e3.has_value() ); + VERIFY( e1.value() == 2 ); + VERIFY( e3.error() == 3 ); + swap(e3, e4); + VERIFY( ! e3.has_value() ); + VERIFY( ! e4.has_value() ); + VERIFY( e3.error() == 4 ); + VERIFY( e4.error() == 3 ); + + std::expected v1(1), v2(2); + std::expected v3(std::unexpect, 3), v4(std::unexpect, 4); + + swap(v1, v2); + VERIFY( v1.value() == 2 ); + VERIFY( v2.value() == 1 ); + swap(v1, v3); + VERIFY( ! v1.has_value() ); + VERIFY( v1.error() == 3 ); + VERIFY( v3.value() == 2 ); + swap(v1, v3); + VERIFY( ! v3.has_value() ); + VERIFY( v1.value() == 2 ); + VERIFY( v3.error() == 3 ); + swap(v3, v4); + VERIFY( ! v3.has_value() ); + VERIFY( ! v4.has_value() ); + VERIFY( v3.error() == 4 ); + VERIFY( v4.error() == 3 ); + + return true; +} + +int main() +{ + static_assert( test_swap() ); + test_swap(); +} diff --git a/libstdc++-v3/testsuite/20_util/expected/synopsis.cc b/libstdc++-v3/testsuite/20_util/expected/synopsis.cc new file mode 100644 index 00000000000..304bae93ebd --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/expected/synopsis.cc @@ -0,0 +1,21 @@ +// { dg-options "-std=gnu++23" } +// { dg-do compile { target c++23 } } + +#include + +#ifndef __cpp_lib_expected +# error "Feature-test macro for expected missing in " +#elif __cpp_lib_expected != 202202L +# error "Feature-test macro for expected has wrong value in " +#endif + +namespace std +{ + template class unexpected; + template class bad_expected_access; + template<> class bad_expected_access; + struct unexpect_t; + extern inline const unexpect_t unexpect; + template class expected; + template requires is_void_v class expected; +} diff --git a/libstdc++-v3/testsuite/20_util/expected/unexpected.cc b/libstdc++-v3/testsuite/20_util/expected/unexpected.cc new file mode 100644 index 00000000000..d4cbeadf674 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/expected/unexpected.cc @@ -0,0 +1,80 @@ +// { dg-options "-std=gnu++23" } +// { dg-do run { target c++23 } } + +#include +#include + +static_assert( sizeof(std::unexpected) == 1 ); + +constexpr bool +test() +{ + std::unexpected u1(1); + VERIFY( u1.error() == 1 ); + + std::unexpected u2(std::in_place, 2); + VERIFY( u2.error() == 2 ); + + struct X + { + constexpr X(int i, int j) : n(i+j) { } + constexpr X(std::initializer_list l, void*) : n(l.size()) { } + + constexpr X(const X&) = default; + constexpr X(X&& x) :n(x.n) { x.n = -1; } + + constexpr X& operator=(const X&) = default; + constexpr X& operator=(X&& x) { n = x.n; x.n = -1; return *this; } + + constexpr bool operator==(const X&) const = default; + constexpr bool operator==(int i) const { return n == i; } + + int n; + }; + + std::unexpected u3(std::in_place, 2, 1); + VERIFY( u3.error() == 3 ); + + std::unexpected u4(std::in_place, {1,2,3,4}, nullptr); + VERIFY( u4.error() == 4 ); + + std::unexpected u5(u4); + VERIFY( u5.error() == 4 ); + VERIFY( u4.error() == 4 ); + + std::unexpected u6(std::move(u4)); + VERIFY( u6.error() == 4 ); + VERIFY( u4.error() == -1 ); + + u6 = u3; + VERIFY( u6.error() == 3 ); + VERIFY( u3.error() == 3 ); + + u5 = std::move(u3); + VERIFY( u5.error() == 3 ); + VERIFY( u3.error() == -1 ); + + u5.swap(u3); + VERIFY( u3.error() == 3 ); + VERIFY( u5.error() == -1 ); + + swap(u5, u3); + VERIFY( u5.error() == 3 ); + VERIFY( u3.error() == -1 ); + + VERIFY( u1 == u1 ); + VERIFY( u1 != u2 ); + VERIFY( u3 == u4 ); + + // CTAD + std::unexpected u7(1L); + static_assert( std::is_same_v> ); + + return true; +} + +int main() +{ + static_assert( test() ); + test(); +} diff --git a/libstdc++-v3/testsuite/20_util/expected/version.cc b/libstdc++-v3/testsuite/20_util/expected/version.cc new file mode 100644 index 00000000000..78dc61ef55d --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/expected/version.cc @@ -0,0 +1,10 @@ +// { dg-options "-std=gnu++23" } +// { dg-do preprocess { target c++23 } } + +#include + +#ifndef __cpp_lib_expected +# error "Feature-test macro for expected missing in " +#elif __cpp_lib_expected != 202202L +# error "Feature-test macro for expected has wrong value in " +#endif