diff --git a/README.md b/README.md index 2a83751..b740686 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,8 @@ The interface is the same as `std::expected` as proposed in [p0323r3](http://www - `or_else`: calls some function if there is no value stored. * `exp.or_else([] { throw std::runtime_error{"oh no"}; });` +p0323r3 specifies calling `.error()` on an expected value, or using the `*` or `->` operators on an unexpected value, to be undefined behaviour. In this implementation it causes an assertion failure. The implementation of assertions can be overridden by defining the macro `TL_ASSERT(boolean_condition)` before #including ; by default, `assert(boolean_condition)` from the `` header is used. Note that correct code would not rely on these assertions. + ### Compiler support Tested on: diff --git a/include/tl/expected.hpp b/include/tl/expected.hpp index b49ff46..f4f4b2c 100644 --- a/include/tl/expected.hpp +++ b/include/tl/expected.hpp @@ -25,6 +25,11 @@ #include #include +#if !defined(TL_ASSERT) +#include +#define TL_ASSERT assert +#endif + #if defined(__EXCEPTIONS) || defined(_CPPUNWIND) #define TL_EXPECTED_EXCEPTIONS_ENABLED #endif @@ -1890,27 +1895,37 @@ public: } } - constexpr const T *operator->() const { return valptr(); } - TL_EXPECTED_11_CONSTEXPR T *operator->() { return valptr(); } + constexpr const T *operator->() const { + TL_ASSERT(has_value()); + return valptr(); + } + TL_EXPECTED_11_CONSTEXPR T *operator->() { + TL_ASSERT(has_value()); + return valptr(); + } template ::value> * = nullptr> constexpr const U &operator*() const & { + TL_ASSERT(has_value()); return val(); } template ::value> * = nullptr> TL_EXPECTED_11_CONSTEXPR U &operator*() & { + TL_ASSERT(has_value()); return val(); } template ::value> * = nullptr> constexpr const U &&operator*() const && { + TL_ASSERT(has_value()); return std::move(val()); } template ::value> * = nullptr> TL_EXPECTED_11_CONSTEXPR U &&operator*() && { + TL_ASSERT(has_value()); return std::move(val()); } @@ -1946,10 +1961,22 @@ public: return std::move(val()); } - constexpr const E &error() const & { return err().value(); } - TL_EXPECTED_11_CONSTEXPR E &error() & { return err().value(); } - constexpr const E &&error() const && { return std::move(err().value()); } - TL_EXPECTED_11_CONSTEXPR E &&error() && { return std::move(err().value()); } + constexpr const E &error() const & { + TL_ASSERT(!has_value()); + return err().value(); + } + TL_EXPECTED_11_CONSTEXPR E &error() & { + TL_ASSERT(!has_value()); + return err().value(); + } + constexpr const E &&error() const && { + TL_ASSERT(!has_value()); + return std::move(err().value()); + } + TL_EXPECTED_11_CONSTEXPR E &&error() && { + TL_ASSERT(!has_value()); + return std::move(err().value()); + } template constexpr T value_or(U &&v) const & { static_assert(std::is_copy_constructible::value && diff --git a/tests/assertions.cpp b/tests/assertions.cpp new file mode 100644 index 0000000..f109097 --- /dev/null +++ b/tests/assertions.cpp @@ -0,0 +1,18 @@ +#include +#include + +#define TL_ASSERT(cond) if (!(cond)) { throw std::runtime_error(std::string("assertion failure")); } + +#include + +TEST_CASE("Assertions", "[assertions]") { + tl::expected o1 = 42; + REQUIRE_THROWS_WITH(o1.error(), "assertion failure"); + + tl::expected o2 {tl::unexpect, 0}; + REQUIRE_THROWS_WITH(*o2, "assertion failure"); + + struct foo { int bar; }; + tl::expected o3 {tl::unexpect, 0}; + REQUIRE_THROWS_WITH(o3->bar, "assertion failure"); +}