diff --git a/README.md b/README.md index e7b45a0..d683e35 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,8 @@ int main() { With this example, we notice that not all functions need to access the underlying `int` element and operate entirely on `even`. So we validate and convert the `int` element to `even` at the very beginning and only fall back to `int` at the very last moment, when we actually need it. Both operations should ideally at the edges of our applications. +Although we reported errors via `std::optional` in the example, it's possible to customise it, e.g. to throw an exception. + ## Requirements C++20 diff --git a/include/rvarago/refined.hpp b/include/rvarago/refined.hpp index 41c04d3..4f7b66b 100644 --- a/include/rvarago/refined.hpp +++ b/include/rvarago/refined.hpp @@ -1,10 +1,60 @@ #include +#include #include #include #include namespace rvarago::refined { +// Error policies for the fallible refinements factory. +namespace error { + +// Models the error reporting mechanism. +template +concept policy = requires(Policy p, Refinement refined) { + typename Refinement::value_type; + typename Refinement::predicate_type; + + // On success. + { p.ok(refined) } -> std::same_as; + + // On error. + { p.err() } -> std::same_as; +}; + +// Report errors as `std::optional`. +template struct to_optional { + using return_type = std::optional; + + // Returns an engaged optional. + constexpr auto ok(Refinement refinement) const -> return_type { + return std::optional{std::move(refinement)}; + } + + // Returns nullopt. + constexpr auto err() const -> return_type { return std::nullopt; } +}; + +// Report errors as exceptions. +template struct to_exception { + using return_type = Refinement; + + struct refinement_exception : std::exception { + const char *what() const noexcept override { + return "fail to refine argument due to unsastified predicate"; + } + }; + + // Returns argument unchanged. + constexpr auto ok(Refinement refinement) const -> return_type { + return refinement; + } + + // Throws a `refinement_exception`. + constexpr auto err() const -> return_type { throw refinement_exception{}; } +}; +} // namespace error + // `refinement` constraints values `t: T` where `Pred{}(t)` holds. template Pred> struct refinement { using value_type = T; @@ -15,13 +65,16 @@ template Pred> struct refinement { // `make(value, pred)` is the only factory to refinements. // - // It returns an engaged optional when `pred(value)` holds. - static constexpr auto make(T value, Pred pred = {}) - -> std::optional> { + // If `pred(value)` holds, then produces a valid instance by delegating to + // `policy.ok`. Else reports error via `policy.err`. + template > Policy = + error::to_optional>> + static constexpr auto make(T value, Pred pred = {}, Policy policy = {}) + -> Policy::return_type { if (std::invoke(std::move(pred), value)) { - return std::optional{refinement{std::move(value)}}; + return policy.ok({refinement{std::move(value)}}); } else { - return std::nullopt; + return policy.err(); } } diff --git a/test/refined_test.cpp b/test/refined_test.cpp index d22336d..047cb8c 100644 --- a/test/refined_test.cpp +++ b/test/refined_test.cpp @@ -2,16 +2,35 @@ #include -using namespace rvarago::refined; +namespace refined = rvarago::refined; + +using even = + refined::refinement; TEST_CASE( "A refinement type must admit only instance that satisfy its predicate", - "[make]") { - using even = - refinement; + "[make][default_policy]") { + + STATIC_REQUIRE(std::is_same_v>); + + STATIC_REQUIRE( + std::is_same_v>(0)), + std::optional>); STATIC_REQUIRE(even::make(0)->value == 0); STATIC_REQUIRE(even::make(1) == std::nullopt); STATIC_REQUIRE(even::make(2)->value == 2); STATIC_REQUIRE(even::make(3) == std::nullopt); } + +TEST_CASE("A to_exception policy should throw on invalid argument", + "[error_policy][to_exception]") { + + STATIC_REQUIRE( + std::is_same_v< + decltype(even::make>(10)), even>); + + STATIC_REQUIRE(even::make>(0).value == 0); + REQUIRE_THROWS_AS(even::make>(1), + refined::error::to_exception::refinement_exception); +}