libstdc++: Define std::expected for C++23 (P0323R12)

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.
This commit is contained in:
Jonathan Wakely 2022-03-24 20:37:13 +00:00
parent d2906412ad
commit b78e0ce28b
16 changed files with 2315 additions and 1 deletions

View File

@ -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 \

View File

@ -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 \

View File

@ -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 \

View File

@ -153,5 +153,6 @@
#endif
#if __cplusplus > 202002L
#include <expected>
#include <spanstream>
#endif

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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) ();

View File

@ -0,0 +1,321 @@
// { dg-options "-std=gnu++23" }
// { dg-do run { target c++23 } }
#include <expected>
#include <type_traits>
#include <testsuite_hooks.h>
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<int> 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<int, int> 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<X, X> 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<void, int> 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<void, X> 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<int, int> e1(1);
std::expected<unsigned, long> 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<void, int> ev4;
std::expected<const void, long> ev5(std::unexpect, 5);
ev4 = ev5;
VERIFY( ! ev4.has_value() );
VERIFY( ev4.error() == 5 );
ev4 = std::expected<volatile void, unsigned>();
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<X, int> e1(0);
e1 = std::unexpected<int>(5);
VERIFY( ! e1.has_value() );
VERIFY( e1.error() == 5 );
VERIFY( check_dtor_count(1) );
e1 = std::unexpected<int>(6);
VERIFY( check_dtor_count(1) );
std::expected<int, X> e2;
std::unexpected<X> 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<int, int> e1(1);
e1.emplace(2);
VERIFY( e1.value() == 2 );
std::expected<void, int> ev2;
ev2.emplace();
VERIFY( ev2.has_value() );
std::expected<X, int> 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<int, X> 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<CopyThrows, MoveThrows> 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<MoveThrows>(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 );
}

View File

@ -0,0 +1,175 @@
// { dg-options "-std=gnu++23" }
// { dg-do run { target c++23 } }
#include <expected>
#include <testsuite_hooks.h>
constexpr bool
test_default()
{
std::expected<int, int> e;
VERIFY( e.has_value() );
VERIFY( *e == 0 );
std::expected<void, int> ev;
VERIFY( ev.has_value() );
VERIFY( (ev.value(), true) );
return true;
}
constexpr bool
test_val()
{
std::expected<int, int> e1(1);
VERIFY( e1.has_value() );
VERIFY( *e1 == 1 );
std::expected<int, int> e2(std::in_place, 2);
VERIFY( e2.has_value() );
VERIFY( *e2 == 2 );
struct X
{
constexpr X(std::initializer_list<int> l, void*) : n(l.size()) { }
int n;
};
std::expected<X, int> e3(X{{1, 2, 3}, nullptr});
VERIFY( e3.has_value() );
VERIFY( e3->n == 3 );
std::expected<X, int> e4(std::in_place, {1, 2, 3, 4}, nullptr);
VERIFY( e4.has_value() );
VERIFY( e4->n == 4 );
std::expected<void, int> ev(std::in_place);
VERIFY( ev.has_value() );
VERIFY( (ev.value(), true) );
return true;
}
constexpr bool
test_err()
{
std::expected<int, int> e1(std::unexpected<int>(1));
VERIFY( ! e1.has_value() );
VERIFY( e1.error() == 1 );
const std::unexpected<int> u2(2);
std::expected<int, int> e2(u2);
VERIFY( ! e2.has_value() );
VERIFY( e2.error() == 2 );
std::expected<int, int> 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<int> l, void*) : n(l.size()) { }
int n;
};
std::expected<int, X> e4(std::unexpect, 1, 3);
VERIFY( ! e4.has_value() );
VERIFY( e4.error().n == 4 );
std::expected<int, X> e5(std::unexpect, {1, 2, 3, 4, 5}, nullptr);
VERIFY( ! e5.has_value() );
VERIFY( e5.error().n == 5 );
std::expected<const void, int> ev1(std::unexpected<int>(1));
VERIFY( ! ev1.has_value() );
VERIFY( ev1.error() == 1 );
std::expected<volatile void, int> ev2(u2);
VERIFY( ! ev2.has_value() );
VERIFY( ev2.error() == 2 );
std::expected<const volatile void, int> ev3(std::unexpect, 3);
VERIFY( ! ev3.has_value() );
VERIFY( ev3.error() == 3 );
std::expected<void, X> ev4(std::unexpect, 1, 3);
VERIFY( ! ev4.has_value() );
VERIFY( ev4.error().n == 4 );
std::expected<void, X> 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<int, int> e1(1);
std::expected<int, int> e2(e1);
VERIFY( e2.value() == 1 );
std::expected<int, int> e3(std::move(e2));
VERIFY( e2.value() == 1 );
VERIFY( e3.value() == 1 );
std::expected<short, short> e4(e1);
VERIFY( e4.value() == 1 );
std::expected<short, short> e5(std::move(e4));
VERIFY( e4.value() == 1 );
VERIFY( e5.value() == 1 );
std::expected<int, int> u1(std::unexpect, 2);
std::expected<int, int> u2(u1);
VERIFY( ! u2.has_value() );
VERIFY( u2.error() == 2 );
std::expected<int, int> u3(std::move(u2));
VERIFY( ! u3.has_value() );
VERIFY( u3.error() == 2 );
std::expected<short, short> u4(u1);
VERIFY( ! u4.has_value() );
VERIFY( u4.error() == 2 );
std::expected<short, short> u5(std::move(u4));
VERIFY( ! u5.has_value() );
VERIFY( u5.error() == 2 );
std::expected<void, int> ev1;
std::expected<void, int> ev2(ev1);
VERIFY( ev2.has_value() );
std::expected<void, int> ev3(std::move(ev2));
VERIFY( ev2.has_value() );
VERIFY( ev3.has_value() );
std::expected<volatile void, short> ev4(ev1);
VERIFY( ev4.has_value() );
std::expected<const void, short> ev5(std::move(ev4));
VERIFY( ev4.has_value() );
VERIFY( ev5.has_value() );
std::expected<void, int> uv1(std::unexpect, 2);
std::expected<void, int> uv2(uv1);
VERIFY( ! uv2.has_value() );
VERIFY( uv2.error() == 2 );
std::expected<void, int> uv3(std::move(uv2));
VERIFY( ! uv3.has_value() );
VERIFY( uv3.error() == 2 );
std::expected<const void, short> uv4(uv1);
VERIFY( ! uv4.has_value() );
VERIFY( uv4.error() == 2 );
std::expected<volatile void, short> 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() );
}

View File

@ -0,0 +1,67 @@
// { dg-options "-std=gnu++23" }
// { dg-do compile { target c++23 } }
#include <expected>
void
test_unexpected()
{
int i[2]{};
// std::unexpected<E> is ill-formed if E is a non-object type,
std::unexpected<int&> ref(i[0]); // { dg-error "here" }
std::unexpected<void()> 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<int[2]> array(i); // { dg-error "here" }
// a specialization of std::unexpected,
std::unexpected<int> u(1);
std::unexpected<std::unexpected<int>> nested(u); // { dg-error "here" }
//
// or a cv-qualified type.
std::unexpected<const int> c_int(1); // { dg-error "here" }
std::unexpected<volatile int> v_int(1); // { dg-error "here" }
}
void
test_expected_value()
{
// std::expected<T, E> is ill-formed if T is a reference type,
std::expected<int&, int> ref(std::unexpect); // { dg-error "here" }
// { dg-error "reference type" "" { target *-*-* } 0 }
// a function type,
std::expected<void(), int> func(std::unexpect); // { dg-error "here" }
// { dg-error "returning a function" "" { target *-*-* } 0 }
//
// possibly cv-qualified types in_place_t,
std::expected<std::in_place_t, int> tag(std::unexpect); // { dg-error "here" }
std::expected<const std::in_place_t, int> ctag(std::unexpect); // { dg-error "here" }
// unexpect_t,
std::expected<std::unexpect_t, int> utag(std::unexpect); // { dg-error "here" }
std::expected<const std::unexpect_t, int> cutag(std::unexpect); // { dg-error "here" }
// or a specialization of unexpected.
std::expected<std::unexpected<int>, int> unex(std::in_place, 1); // { dg-error "here" }
std::expected<const std::unexpected<int>, int> cunex(std::in_place, 1); // { dg-error "here" }
}
void
test_expected_error()
{
// std::expected<T, E> is ill-formed if std::unexpected<E> would be
// ill-formed. Test the same types as in test_unexpected().
std::expected<int, int&> ref; // { dg-error "here" }
std::expected<int, void()> func; // { dg-error "here" }
std::expected<int, int[2]> array; // { dg-error "here" }
std::expected<int, std::unexpected<int>> nested; // { dg-error "here" }
std::expected<int, const int> c_int; // { dg-error "here" }
std::expected<int, volatile int> v_int; // { dg-error "here" }
}
// { dg-prune-output "static assertion failed" }

View File

@ -0,0 +1,209 @@
// { dg-options "-std=gnu++23" }
// { dg-do run { target c++23 } }
#include <expected>
#include <expected>
#include <testsuite_hooks.h>
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<X, int> e1;
VERIFY( e1->f() == 1 );
const auto& e2 = e1;
VERIFY( e2->f() == 2 );
return true;
}
constexpr bool
test_star()
{
std::expected<X, int> 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<void, int> v;
*v;
return true;
}
constexpr bool
test_has_value()
{
std::expected<int, int> e;
VERIFY( e.has_value() );
VERIFY( e );
e = std::unexpected(1);
VERIFY( ! e.has_value() );
VERIFY( ! e );
std::expected<void, int> 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<X, int> 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<void, int> v1;
v1.value();
std::move(v1).value();
return true;
}
void
test_value_throw()
{
std::expected<int, int> e1 = std::unexpected(9);
try {
e1.value();
VERIFY( false );
} catch (const std::bad_expected_access<int>& e) {
VERIFY( e.error() == 9 );
}
try {
std::move(e1).value();
VERIFY( false );
} catch (const std::bad_expected_access<int>& e) {
VERIFY( e.error() == 9 );
}
const auto& e2 = e1;
try {
e2.value();
VERIFY( false );
} catch (const std::bad_expected_access<int>& e) {
VERIFY( e.error() == 9 );
}
try {
std::move(e2).value();
VERIFY( false );
} catch (const std::bad_expected_access<int>& e) {
VERIFY( e.error() == 9 );
}
std::expected<void, int> v1 = std::unexpected(8);
try {
v1.value();
VERIFY( false );
} catch (const std::bad_expected_access<int>& e) {
VERIFY( e.error() == 8 );
}
try {
std::move(v1).value();
VERIFY( false );
} catch (const std::bad_expected_access<int>& e) {
VERIFY( e.error() == 8 );
}
}
constexpr bool
test_error()
{
std::expected<int, X> 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<void, X> 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<Movable, int> 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();
}

View File

@ -0,0 +1,129 @@
// { dg-options "-std=gnu++23" }
// { dg-do compile { target c++23 } }
#include <expected>
#include <type_traits>
// Default construction
template<typename T, typename E>
constexpr bool default_constructible
= std::is_default_constructible_v<std::expected<T, E>>;
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<typename T, typename E>
constexpr bool trivially_destructible
= std::is_trivially_destructible_v<std::expected<T, E>>;
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<typename T, typename E>
constexpr Result copy_constructible
= std::is_trivially_copy_constructible_v<std::expected<T, E>> ? Trivial
: std::is_copy_constructible_v<std::expected<T, E>> ? 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<typename T, typename E>
constexpr Result move_constructible
= std::is_trivially_move_constructible_v<std::expected<T, E>> ? Trivial
: std::is_nothrow_move_constructible_v<std::expected<T, E>> ? NoThrow
: std::is_move_constructible_v<std::expected<T, E>> ? 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<typename T, typename E>
constexpr bool copy_assignable
= std::is_copy_assignable_v<std::expected<T, E>>;
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<typename T, typename E>
constexpr bool move_assignable
= std::is_move_assignable_v<std::expected<T, E>>;
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<char, unsigned char>) == 2 );
static_assert( sizeof(std::expected<void, char>) == 2 );
static_assert( sizeof(std::expected<void*, char>) == 2 * __alignof(void*) );
static_assert( alignof(std::expected<void, char>) == 1 );
static_assert( alignof(std::expected<void*, char>) == alignof(void*) );

View File

@ -0,0 +1,57 @@
// { dg-options "-std=gnu++23" }
// { dg-do run { target c++23 } }
#include <expected>
#include <testsuite_hooks.h>
constexpr bool
test_swap()
{
std::expected<int, int> e1(1), e2(2);
std::expected<int, int> 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<int, int> v1(1), v2(2);
std::expected<int, int> 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();
}

View File

@ -0,0 +1,21 @@
// { dg-options "-std=gnu++23" }
// { dg-do compile { target c++23 } }
#include <expected>
#ifndef __cpp_lib_expected
# error "Feature-test macro for expected missing in <expected>"
#elif __cpp_lib_expected != 202202L
# error "Feature-test macro for expected has wrong value in <expected>"
#endif
namespace std
{
template<class E> class unexpected;
template<class E> class bad_expected_access;
template<> class bad_expected_access<void>;
struct unexpect_t;
extern inline const unexpect_t unexpect;
template<class T, class E> class expected;
template<class T, class E> requires is_void_v<T> class expected<T, E>;
}

View File

@ -0,0 +1,80 @@
// { dg-options "-std=gnu++23" }
// { dg-do run { target c++23 } }
#include <expected>
#include <testsuite_hooks.h>
static_assert( sizeof(std::unexpected<char>) == 1 );
constexpr bool
test()
{
std::unexpected<int> u1(1);
VERIFY( u1.error() == 1 );
std::unexpected<int> 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<int> 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<X> u3(std::in_place, 2, 1);
VERIFY( u3.error() == 3 );
std::unexpected<X> u4(std::in_place, {1,2,3,4}, nullptr);
VERIFY( u4.error() == 4 );
std::unexpected<X> u5(u4);
VERIFY( u5.error() == 4 );
VERIFY( u4.error() == 4 );
std::unexpected<X> 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<decltype(u7), std::unexpected<long>> );
return true;
}
int main()
{
static_assert( test() );
test();
}

View File

@ -0,0 +1,10 @@
// { dg-options "-std=gnu++23" }
// { dg-do preprocess { target c++23 } }
#include <version>
#ifndef __cpp_lib_expected
# error "Feature-test macro for expected missing in <version>"
#elif __cpp_lib_expected != 202202L
# error "Feature-test macro for expected has wrong value in <version>"
#endif