Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ With this example, we notice that not all functions need access to the underlyin

Although we reported errors via `std::optional` in the example, we can customise it, e.g. to throw an exception with the built-in `even::make<refined::error::to_exception>` or define a whole user-provided policy.

Furthermore, with `rvarago::refined::traits` we can extend a refinement with properties, such as ordered with the spaceship operator.

## Requirements

C++20
Expand All @@ -63,9 +65,6 @@ C++20

This is a header-only library. See [`refined.hpp`](include/rvarago/refined.hpp).

- (Optional) Link against the INTERFACE `rvarago::refined` target in your CMake build.
- Include `rvarago/refined.hpp`

## Contributing

This repository has a [`flake.nix`](./flake.nix) with everything I need for development/CI (toolchain, language server, etc).
Expand Down
21 changes: 20 additions & 1 deletion include/rvarago/refined.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,17 @@

namespace rvarago::refined {

template <typename T, std::predicate<T const &> auto Pred, typename... Bases>
// Properties a refinement implements.
struct traits {
// Equality comparison (`==`, `!=`).
bool equality{false};

// Comparison operations (`<`, `>`, etc) with the spaceship operator.
bool ordered{false};
};

template <typename T, std::predicate<T const &> auto Pred, traits Traits = {},
typename... Bases>
requires((std::is_same_v<T, typename Bases::value_type> && ...) &&
(std::predicate<typename Bases::predicate_type, T const &> && ...))
class refinement {
Expand Down Expand Up @@ -57,6 +67,15 @@ class refinement {
return refinement{std::move(value)};
}

friend constexpr auto operator==(refinement const &, refinement const &)
-> bool
requires(Traits.equality)
= default;

friend constexpr auto operator<=>(refinement const &, refinement const &)
requires(Traits.ordered)
= default;

private:
explicit constexpr refinement(T value) : value_{std::move(value)} {}

Expand Down
22 changes: 21 additions & 1 deletion test/refined_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@ TEST_CASE("Two refinement types with same ground types and nominally defined "
"subtyping are implicily convertible",
"[predicate_subtyping]") {
using even_lt_10 =
refined::refinement<int, [](auto const x) { return x < 10; }, even>;
refined::refinement<int, [](auto const x) { return x < 10; },
refined::traits{.ordered = true}, even>;

STATIC_REQUIRE(std::is_constructible_v<even, even_lt_10>);

constexpr even valid = *even_lt_10::make(8);

STATIC_REQUIRE(valid.value() == 8);

constexpr std::optional<even> invalid = even_lt_10::make(10);
Expand All @@ -56,3 +58,21 @@ TEST_CASE("A to_exception policy should throw on invalid argument",
REQUIRE_THROWS_AS(even::make<refined::error::to_exception>(1),
refined::error::to_exception::refinement_exception);
}

TEST_CASE("A refinement can be equality comparable", "[traits][equality]") {

using ref_cmp = refined::refinement<int, [](auto const &) { return true; },
refined::traits{.equality = true}>;

STATIC_REQUIRE(*ref_cmp::make(1) == *ref_cmp::make(1));
STATIC_REQUIRE(*ref_cmp::make(1) != *ref_cmp::make(2));
}

TEST_CASE("A refinement can be ordered", "[traits][sorted]") {

using ref_cmp = refined::refinement<int, [](auto const &) { return true; },
refined::traits{.ordered = true}>;

STATIC_REQUIRE(*ref_cmp::make(1) < *ref_cmp::make(2));
STATIC_REQUIRE(!(*ref_cmp::make(2) < *ref_cmp::make(1)));
}