Skip to content

Commit af83824

Browse files
committed
Merge fix5-preconditions-and-minor: hardened preconditions, swap constraint, minor fixes
2 parents 0b2b1a8 + 0f035d2 commit af83824

4 files changed

Lines changed: 215 additions & 18 deletions

File tree

include/beman/expected/expected.hpp

Lines changed: 90 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -752,31 +752,55 @@ constexpr void expected<T, E>::swap(expected& rhs) noexcept(std::is_nothrow_move
752752

753753
template <class T, class E>
754754
constexpr const T* expected<T, E>::operator->() const noexcept {
755+
#if defined(BEMAN_EXPECTED_HARDENED)
756+
if (!has_val_)
757+
__builtin_trap();
758+
#endif
755759
return std::addressof(val_);
756760
}
757761

758762
template <class T, class E>
759763
constexpr T* expected<T, E>::operator->() noexcept {
764+
#if defined(BEMAN_EXPECTED_HARDENED)
765+
if (!has_val_)
766+
__builtin_trap();
767+
#endif
760768
return std::addressof(val_);
761769
}
762770

763771
template <class T, class E>
764772
constexpr const T& expected<T, E>::operator*() const& noexcept {
773+
#if defined(BEMAN_EXPECTED_HARDENED)
774+
if (!has_val_)
775+
__builtin_trap();
776+
#endif
765777
return val_;
766778
}
767779

768780
template <class T, class E>
769781
constexpr T& expected<T, E>::operator*() & noexcept {
782+
#if defined(BEMAN_EXPECTED_HARDENED)
783+
if (!has_val_)
784+
__builtin_trap();
785+
#endif
770786
return val_;
771787
}
772788

773789
template <class T, class E>
774790
constexpr const T&& expected<T, E>::operator*() const&& noexcept {
791+
#if defined(BEMAN_EXPECTED_HARDENED)
792+
if (!has_val_)
793+
__builtin_trap();
794+
#endif
775795
return std::move(val_);
776796
}
777797

778798
template <class T, class E>
779799
constexpr T&& expected<T, E>::operator*() && noexcept {
800+
#if defined(BEMAN_EXPECTED_HARDENED)
801+
if (!has_val_)
802+
__builtin_trap();
803+
#endif
780804
return std::move(val_);
781805
}
782806

@@ -826,21 +850,37 @@ constexpr T&& expected<T, E>::value() && {
826850

827851
template <class T, class E>
828852
constexpr const E& expected<T, E>::error() const& noexcept {
853+
#if defined(BEMAN_EXPECTED_HARDENED)
854+
if (has_val_)
855+
__builtin_trap();
856+
#endif
829857
return unex_;
830858
}
831859

832860
template <class T, class E>
833861
constexpr E& expected<T, E>::error() & noexcept {
862+
#if defined(BEMAN_EXPECTED_HARDENED)
863+
if (has_val_)
864+
__builtin_trap();
865+
#endif
834866
return unex_;
835867
}
836868

837869
template <class T, class E>
838870
constexpr const E&& expected<T, E>::error() const&& noexcept {
871+
#if defined(BEMAN_EXPECTED_HARDENED)
872+
if (has_val_)
873+
__builtin_trap();
874+
#endif
839875
return std::move(unex_);
840876
}
841877

842878
template <class T, class E>
843879
constexpr E&& expected<T, E>::error() && noexcept {
880+
#if defined(BEMAN_EXPECTED_HARDENED)
881+
if (has_val_)
882+
__builtin_trap();
883+
#endif
844884
return std::move(unex_);
845885
}
846886

@@ -1304,15 +1344,44 @@ class expected<T, E> {
13041344
constexpr explicit operator bool() const noexcept { return has_val_; }
13051345
constexpr bool has_value() const noexcept { return has_val_; }
13061346

1307-
constexpr void operator*() const noexcept {}
1347+
constexpr void operator*() const noexcept {
1348+
#if defined(BEMAN_EXPECTED_HARDENED)
1349+
if (!has_val_)
1350+
__builtin_trap();
1351+
#endif
1352+
}
13081353

13091354
constexpr void value() const&;
13101355
constexpr void value() &&;
13111356

1312-
constexpr const E& error() const& noexcept { return unex_; }
1313-
constexpr E& error() & noexcept { return unex_; }
1314-
constexpr const E&& error() const&& noexcept { return std::move(unex_); }
1315-
constexpr E&& error() && noexcept { return std::move(unex_); }
1357+
constexpr const E& error() const& noexcept {
1358+
#if defined(BEMAN_EXPECTED_HARDENED)
1359+
if (has_val_)
1360+
__builtin_trap();
1361+
#endif
1362+
return unex_;
1363+
}
1364+
constexpr E& error() & noexcept {
1365+
#if defined(BEMAN_EXPECTED_HARDENED)
1366+
if (has_val_)
1367+
__builtin_trap();
1368+
#endif
1369+
return unex_;
1370+
}
1371+
constexpr const E&& error() const&& noexcept {
1372+
#if defined(BEMAN_EXPECTED_HARDENED)
1373+
if (has_val_)
1374+
__builtin_trap();
1375+
#endif
1376+
return std::move(unex_);
1377+
}
1378+
constexpr E&& error() && noexcept {
1379+
#if defined(BEMAN_EXPECTED_HARDENED)
1380+
if (has_val_)
1381+
__builtin_trap();
1382+
#endif
1383+
return std::move(unex_);
1384+
}
13161385

13171386
template <class G = E>
13181387
constexpr E error_or(G&& def) const&;
@@ -1715,7 +1784,8 @@ template <class F>
17151784
constexpr auto expected<T, E>::or_else(F&& f) & {
17161785
using G = std::remove_cvref_t<std::invoke_result_t<F, E&>>;
17171786
static_assert(detail::is_expected_specialization<G>::value, "or_else: F must return a specialization of expected");
1718-
static_assert(std::is_void_v<typename G::value_type>, "or_else: F must return expected with void value_type");
1787+
static_assert(std::is_same_v<typename G::value_type, T>,
1788+
"or_else: F must return expected with the same value_type");
17191789
if (has_val_)
17201790
return G();
17211791
return std::invoke(std::forward<F>(f), unex_);
@@ -1727,7 +1797,8 @@ template <class F>
17271797
constexpr auto expected<T, E>::or_else(F&& f) && {
17281798
using G = std::remove_cvref_t<std::invoke_result_t<F, E&&>>;
17291799
static_assert(detail::is_expected_specialization<G>::value, "or_else: F must return a specialization of expected");
1730-
static_assert(std::is_void_v<typename G::value_type>, "or_else: F must return expected with void value_type");
1800+
static_assert(std::is_same_v<typename G::value_type, T>,
1801+
"or_else: F must return expected with the same value_type");
17311802
if (has_val_)
17321803
return G();
17331804
return std::invoke(std::forward<F>(f), std::move(unex_));
@@ -1739,7 +1810,8 @@ template <class F>
17391810
constexpr auto expected<T, E>::or_else(F&& f) const& {
17401811
using G = std::remove_cvref_t<std::invoke_result_t<F, const E&>>;
17411812
static_assert(detail::is_expected_specialization<G>::value, "or_else: F must return a specialization of expected");
1742-
static_assert(std::is_void_v<typename G::value_type>, "or_else: F must return expected with void value_type");
1813+
static_assert(std::is_same_v<typename G::value_type, T>,
1814+
"or_else: F must return expected with the same value_type");
17431815
if (has_val_)
17441816
return G();
17451817
return std::invoke(std::forward<F>(f), unex_);
@@ -1751,7 +1823,8 @@ template <class F>
17511823
constexpr auto expected<T, E>::or_else(F&& f) const&& {
17521824
using G = std::remove_cvref_t<std::invoke_result_t<F, const E&&>>;
17531825
static_assert(detail::is_expected_specialization<G>::value, "or_else: F must return a specialization of expected");
1754-
static_assert(std::is_void_v<typename G::value_type>, "or_else: F must return expected with void value_type");
1826+
static_assert(std::is_same_v<typename G::value_type, T>,
1827+
"or_else: F must return expected with the same value_type");
17551828
if (has_val_)
17561829
return G();
17571830
return std::invoke(std::forward<F>(f), std::move(unex_));
@@ -1872,8 +1945,8 @@ constexpr auto expected<T, E>::transform_error(F&& f) & {
18721945
static_assert(!detail::is_unexpected_specialization<G>::value,
18731946
"transform_error: G must not be a specialization of unexpected");
18741947
if (has_val_)
1875-
return expected<void, G>();
1876-
return expected<void, G>(unexpect, std::invoke(std::forward<F>(f), unex_));
1948+
return expected<T, G>();
1949+
return expected<T, G>(unexpect, std::invoke(std::forward<F>(f), unex_));
18771950
}
18781951

18791952
template <class T, class E>
@@ -1887,8 +1960,8 @@ constexpr auto expected<T, E>::transform_error(F&& f) && {
18871960
static_assert(!detail::is_unexpected_specialization<G>::value,
18881961
"transform_error: G must not be a specialization of unexpected");
18891962
if (has_val_)
1890-
return expected<void, G>();
1891-
return expected<void, G>(unexpect, std::invoke(std::forward<F>(f), std::move(unex_)));
1963+
return expected<T, G>();
1964+
return expected<T, G>(unexpect, std::invoke(std::forward<F>(f), std::move(unex_)));
18921965
}
18931966

18941967
template <class T, class E>
@@ -1902,8 +1975,8 @@ constexpr auto expected<T, E>::transform_error(F&& f) const& {
19021975
static_assert(!detail::is_unexpected_specialization<G>::value,
19031976
"transform_error: G must not be a specialization of unexpected");
19041977
if (has_val_)
1905-
return expected<void, G>();
1906-
return expected<void, G>(unexpect, std::invoke(std::forward<F>(f), unex_));
1978+
return expected<T, G>();
1979+
return expected<T, G>(unexpect, std::invoke(std::forward<F>(f), unex_));
19071980
}
19081981

19091982
template <class T, class E>
@@ -1917,8 +1990,8 @@ constexpr auto expected<T, E>::transform_error(F&& f) const&& {
19171990
static_assert(!detail::is_unexpected_specialization<G>::value,
19181991
"transform_error: G must not be a specialization of unexpected");
19191992
if (has_val_)
1920-
return expected<void, G>();
1921-
return expected<void, G>(unexpect, std::invoke(std::forward<F>(f), std::move(unex_)));
1993+
return expected<T, G>();
1994+
return expected<T, G>(unexpect, std::invoke(std::forward<F>(f), std::move(unex_)));
19221995
}
19231996

19241997
} // namespace expected

include/beman/expected/unexpected.hpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,11 @@ class unexpected {
7171
return x.unex_ == y.error();
7272
}
7373

74-
friend constexpr void swap(unexpected& x, unexpected& y) noexcept(noexcept(x.swap(y))) { x.swap(y); }
74+
friend constexpr void swap(unexpected& x, unexpected& y) noexcept(noexcept(x.swap(y)))
75+
requires std::is_swappable_v<E>
76+
{
77+
x.swap(y);
78+
}
7579

7680
private:
7781
E unex_;

tests/beman/expected/CMakeLists.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,19 @@ add_fail_test(expected_bool_value_ctor_from_expected_fail expected_bool_value_ct
7878

7979
# Fix 4 — Mandates: T must not be a specialization of unexpected
8080
add_fail_test(expected_unexpected_value_type_fail expected_unexpected_value_type_fail.cpp)
81+
82+
# =============================================================================
83+
# Hardened precondition tests (compiled with -DBEMAN_EXPECTED_HARDENED)
84+
# =============================================================================
85+
86+
add_executable(beman.expected.tests.hardened)
87+
target_sources(beman.expected.tests.hardened PRIVATE expected_hardened.test.cpp)
88+
target_link_libraries(
89+
beman.expected.tests.hardened
90+
PRIVATE beman::expected Catch2::Catch2WithMain
91+
)
92+
target_compile_definitions(
93+
beman.expected.tests.hardened
94+
PRIVATE BEMAN_EXPECTED_HARDENED
95+
)
96+
catch_discover_tests(beman.expected.tests.hardened)
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// tests/beman/expected/expected_hardened.test.cpp -*-C++-*-
2+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3+
4+
// Beman-only: compiled with -DBEMAN_EXPECTED_HARDENED to verify
5+
// precondition-check code compiles and happy paths work correctly.
6+
7+
#include <beman/expected/expected.hpp>
8+
9+
#include <catch2/catch_test_macros.hpp>
10+
11+
#include <string>
12+
13+
using namespace beman::expected;
14+
15+
// ---------------------------------------------------------------------------
16+
// Primary template: operator-> happy path
17+
// ---------------------------------------------------------------------------
18+
19+
TEST_CASE("hardened: operator-> on value-state expected", "[hardened]") {
20+
expected<std::string, int> e("hello");
21+
CHECK(e->size() == 5);
22+
23+
const expected<std::string, int> ce("world");
24+
CHECK(ce->size() == 5);
25+
}
26+
27+
// ---------------------------------------------------------------------------
28+
// Primary template: operator* happy path
29+
// ---------------------------------------------------------------------------
30+
31+
TEST_CASE("hardened: operator* on value-state expected", "[hardened]") {
32+
expected<int, int> e(42);
33+
CHECK(*e == 42);
34+
35+
const expected<int, int> ce(99);
36+
CHECK(*ce == 99);
37+
38+
CHECK(*std::move(e) == 42);
39+
40+
const expected<int, int> ce2(7);
41+
CHECK(*std::move(ce2) == 7);
42+
}
43+
44+
// ---------------------------------------------------------------------------
45+
// Primary template: error() happy path
46+
// ---------------------------------------------------------------------------
47+
48+
TEST_CASE("hardened: error() on error-state expected", "[hardened]") {
49+
expected<int, int> e(unexpect, 42);
50+
CHECK(e.error() == 42);
51+
52+
const expected<int, int> ce(unexpect, 99);
53+
CHECK(ce.error() == 99);
54+
55+
expected<int, int> e2(unexpect, 7);
56+
CHECK(std::move(e2).error() == 7);
57+
58+
const expected<int, int> ce2(unexpect, 13);
59+
CHECK(std::move(ce2).error() == 13);
60+
}
61+
62+
// ---------------------------------------------------------------------------
63+
// Void specialization: operator* happy path
64+
// ---------------------------------------------------------------------------
65+
66+
TEST_CASE("hardened: operator* on value-state expected<void,int>", "[hardened]") {
67+
expected<void, int> e;
68+
*e;
69+
70+
const expected<void, int> ce;
71+
*ce;
72+
}
73+
74+
// ---------------------------------------------------------------------------
75+
// Void specialization: error() happy path
76+
// ---------------------------------------------------------------------------
77+
78+
TEST_CASE("hardened: error() on error-state expected<void,int>", "[hardened]") {
79+
expected<void, int> e(unexpect, 42);
80+
CHECK(e.error() == 42);
81+
82+
const expected<void, int> ce(unexpect, 99);
83+
CHECK(ce.error() == 99);
84+
85+
expected<void, int> e2(unexpect, 7);
86+
CHECK(std::move(e2).error() == 7);
87+
88+
const expected<void, int> ce2(unexpect, 13);
89+
CHECK(std::move(ce2).error() == 13);
90+
}
91+
92+
// ---------------------------------------------------------------------------
93+
// unexpected friend swap: constraint check (beman-only)
94+
// ---------------------------------------------------------------------------
95+
96+
struct NonSwappable {
97+
NonSwappable() = default;
98+
NonSwappable(const NonSwappable&) = delete;
99+
NonSwappable(NonSwappable&&) = delete;
100+
NonSwappable& operator=(const NonSwappable&) = delete;
101+
NonSwappable& operator=(NonSwappable&&) = delete;
102+
};
103+
static_assert(!std::is_swappable_v<NonSwappable>);
104+
static_assert(!std::is_swappable_v<unexpected<NonSwappable>>);

0 commit comments

Comments
 (0)