From 733cfe8bef6791377a9a72267ae4908c9eb36487 Mon Sep 17 00:00:00 2001 From: Christoph Froehlich Date: Sun, 14 Jun 2026 19:33:11 +0000 Subject: [PATCH 01/12] Fix include guards --- include/rsl/algorithm.hpp | 5 ++++- include/rsl/monad.hpp | 13 ++++++++----- include/rsl/no_discard.hpp | 5 ++++- include/rsl/overload.hpp | 5 ++++- include/rsl/parameter_validators.hpp | 29 +++++++++++++++------------- include/rsl/queue.hpp | 5 ++++- include/rsl/random.hpp | 5 ++++- include/rsl/static_string.hpp | 5 ++++- include/rsl/static_vector.hpp | 5 ++++- include/rsl/strong_type.hpp | 5 ++++- include/rsl/try.hpp | 5 ++++- 11 files changed, 60 insertions(+), 27 deletions(-) diff --git a/include/rsl/algorithm.hpp b/include/rsl/algorithm.hpp index d3b3c0f..d59aeac 100644 --- a/include/rsl/algorithm.hpp +++ b/include/rsl/algorithm.hpp @@ -1,4 +1,5 @@ -#pragma once +#ifndef RSL_ALGORITHM_HPP_ +#define RSL_ALGORITHM_HPP_ #include @@ -40,3 +41,5 @@ template } } // namespace rsl + +#endif // RSL_ALGORITHM_HPP_ diff --git a/include/rsl/monad.hpp b/include/rsl/monad.hpp index d22eb42..8eef062 100644 --- a/include/rsl/monad.hpp +++ b/include/rsl/monad.hpp @@ -1,4 +1,5 @@ -#pragma once +#ifndef RSL_MONAD_HPP_ +#define RSL_MONAD_HPP_ #include @@ -20,8 +21,8 @@ namespace rsl { * @return Return type of fn */ template -[[nodiscard]] constexpr auto mbind(std::optional const& opt, - Fn fn) -> std::invoke_result_t { +[[nodiscard]] constexpr auto mbind(std::optional const& opt, Fn fn) + -> std::invoke_result_t { static_assert(std::is_convertible_v>, "Fn must return a std::optional"); if (opt) return fn(opt.value()); @@ -41,8 +42,8 @@ template * @return Return type of the function */ template -[[nodiscard]] constexpr auto mbind(tl::expected const& exp, - Fn fn) -> std::invoke_result_t { +[[nodiscard]] constexpr auto mbind(tl::expected const& exp, Fn fn) + -> std::invoke_result_t { if (exp) return fn(exp.value()); return tl::unexpected(exp.error()); } @@ -205,3 +206,5 @@ template , std::invoke_result_t> { return std::invoke(std::forward(fn), std::forward(val)); } + +#endif // RSL_MONAD_HPP_ diff --git a/include/rsl/no_discard.hpp b/include/rsl/no_discard.hpp index 418eb43..2e0af77 100644 --- a/include/rsl/no_discard.hpp +++ b/include/rsl/no_discard.hpp @@ -1,4 +1,5 @@ -#pragma once +#ifndef RSL_NO_DISCARD_HPP_ +#define RSL_NO_DISCARD_HPP_ #include @@ -26,3 +27,5 @@ class NoDiscard { }; } // namespace rsl + +#endif // RSL_NO_DISCARD_HPP_ diff --git a/include/rsl/overload.hpp b/include/rsl/overload.hpp index 7bb92d9..0ef37a6 100644 --- a/include/rsl/overload.hpp +++ b/include/rsl/overload.hpp @@ -1,4 +1,5 @@ -#pragma once +#ifndef RSL_OVERLOAD_HPP_ +#define RSL_OVERLOAD_HPP_ namespace rsl { @@ -18,3 +19,5 @@ template Overload(Ts...) -> Overload; } // namespace rsl + +#endif // RSL_OVERLOAD_HPP_ diff --git a/include/rsl/parameter_validators.hpp b/include/rsl/parameter_validators.hpp index b1fd52f..88206b3 100644 --- a/include/rsl/parameter_validators.hpp +++ b/include/rsl/parameter_validators.hpp @@ -1,4 +1,5 @@ -#pragma once +#ifndef RSL_PARAMETER_VALIDATORS_HPP_ +#define RSL_PARAMETER_VALIDATORS_HPP_ #include #include @@ -36,8 +37,8 @@ template template [[nodiscard]] auto size_compare(rclcpp::Parameter const& parameter, size_t const size, - std::string const& predicate_description, - Fn const& predicate) -> tl::expected { + std::string const& predicate_description, Fn const& predicate) + -> tl::expected { static constexpr auto format_string = "Length of parameter '{}' is '{}' but must be {} '{}'"; switch (parameter.get_type()) { case rclcpp::ParameterType::PARAMETER_STRING: @@ -55,8 +56,8 @@ template template [[nodiscard]] auto compare(rclcpp::Parameter const& parameter, T const& value, - std::string const& predicate_description, - Fn const& predicate) -> tl::expected { + std::string const& predicate_description, Fn const& predicate) + -> tl::expected { if (auto const param_value = parameter.get_value(); !predicate(param_value, value)) return tl::unexpected(fmt::format("Parameter '{}' with the value '{}' must be {} '{}'", parameter.get_name(), stringify(param_value), @@ -182,8 +183,8 @@ template * @return Help string if the parameter is invalid, otherwise void */ template -[[nodiscard]] auto lower_element_bounds(rclcpp::Parameter const& parameter, - T const& lower) -> tl::expected { +[[nodiscard]] auto lower_element_bounds(rclcpp::Parameter const& parameter, T const& lower) + -> tl::expected { auto const& param_value = parameter.get_value>(); for (auto val : param_value) if (val < lower) @@ -200,8 +201,8 @@ template * @return Help string if the parameter is invalid, otherwise void */ template -[[nodiscard]] auto upper_element_bounds(rclcpp::Parameter const& parameter, - T const& upper) -> tl::expected { +[[nodiscard]] auto upper_element_bounds(rclcpp::Parameter const& parameter, T const& upper) + -> tl::expected { auto const& param_value = parameter.get_value>(); for (auto val : param_value) if (val > upper) @@ -218,8 +219,8 @@ template * @return Help string if the parameter is invalid, otherwise void */ template -[[nodiscard]] auto bounds(rclcpp::Parameter const& parameter, T const& lower, - T const& upper) -> tl::expected { +[[nodiscard]] auto bounds(rclcpp::Parameter const& parameter, T const& lower, T const& upper) + -> tl::expected { auto const& param_value = parameter.get_value(); if (param_value < lower || param_value > upper) return tl::unexpected( @@ -304,8 +305,8 @@ template * @return Help string if the parameter is invalid, otherwise void */ template -[[nodiscard]] auto one_of(rclcpp::Parameter const& parameter, - std::vector const& collection) -> tl::expected { +[[nodiscard]] auto one_of(rclcpp::Parameter const& parameter, std::vector const& collection) + -> tl::expected { auto const& param_value = parameter.get_value(); if (contains(collection, param_value)) return {}; return tl::unexpected(fmt::format( @@ -320,3 +321,5 @@ template -> rcl_interfaces::msg::SetParametersResult; } // namespace rsl + +#endif // RSL_PARAMETER_VALIDATORS_HPP_ diff --git a/include/rsl/queue.hpp b/include/rsl/queue.hpp index 263f604..613b4bc 100644 --- a/include/rsl/queue.hpp +++ b/include/rsl/queue.hpp @@ -1,4 +1,5 @@ -#pragma once +#ifndef RSL_QUEUE_HPP_ +#define RSL_QUEUE_HPP_ #include #include @@ -77,3 +78,5 @@ class Queue { } }; } // namespace rsl + +#endif // RSL_QUEUE_HPP_ diff --git a/include/rsl/random.hpp b/include/rsl/random.hpp index e431d7f..a07d7b3 100644 --- a/include/rsl/random.hpp +++ b/include/rsl/random.hpp @@ -1,4 +1,5 @@ -#pragma once +#ifndef RSL_RANDOM_HPP_ +#define RSL_RANDOM_HPP_ #include @@ -68,3 +69,5 @@ template [[nodiscard]] RSL_EXPORT auto random_unit_quaternion() -> Eigen::Quaterniond; } // namespace rsl + +#endif // RSL_RANDOM_HPP_ diff --git a/include/rsl/static_string.hpp b/include/rsl/static_string.hpp index 34450b1..966b1b7 100644 --- a/include/rsl/static_string.hpp +++ b/include/rsl/static_string.hpp @@ -1,4 +1,5 @@ -#pragma once +#ifndef RSL_STATIC_STRING_HPP_ +#define RSL_STATIC_STRING_HPP_ #include #include @@ -58,3 +59,5 @@ template } } // namespace rsl + +#endif // RSL_STATIC_STRING_HPP_ diff --git a/include/rsl/static_vector.hpp b/include/rsl/static_vector.hpp index ee6b8c2..c7e3fbb 100644 --- a/include/rsl/static_vector.hpp +++ b/include/rsl/static_vector.hpp @@ -1,4 +1,5 @@ -#pragma once +#ifndef RSL_STATIC_VECTOR_HPP_ +#define RSL_STATIC_VECTOR_HPP_ #include @@ -82,3 +83,5 @@ template } } // namespace rsl + +#endif // RSL_STATIC_VECTOR_HPP_ diff --git a/include/rsl/strong_type.hpp b/include/rsl/strong_type.hpp index a21d0d2..a229e9e 100644 --- a/include/rsl/strong_type.hpp +++ b/include/rsl/strong_type.hpp @@ -1,4 +1,5 @@ -#pragma once +#ifndef RSL_STRONG_TYPE_HPP_ +#define RSL_STRONG_TYPE_HPP_ #include @@ -39,3 +40,5 @@ class StrongType { }; } // namespace rsl + +#endif // RSL_STRONG_TYPE_HPP_ diff --git a/include/rsl/try.hpp b/include/rsl/try.hpp index 51980a6..6904238 100644 --- a/include/rsl/try.hpp +++ b/include/rsl/try.hpp @@ -1,4 +1,5 @@ -#pragma once +#ifndef RSL_TRY_HPP_ +#define RSL_TRY_HPP_ #include @@ -33,3 +34,5 @@ if (!_expected.has_value()) return tl::unexpected(_expected.error()); \ _expected.value(); \ }) + +#endif // RSL_TRY_HPP_ From 37c2e6e09682f69bfe48cfee059ace8b5bde582a Mon Sep 17 00:00:00 2001 From: Christoph Froehlich Date: Sun, 14 Jun 2026 19:34:24 +0000 Subject: [PATCH 02/12] Silcence C++20 requires constraints error --- include/rsl/monad.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/rsl/monad.hpp b/include/rsl/monad.hpp index 8eef062..c0531de 100644 --- a/include/rsl/monad.hpp +++ b/include/rsl/monad.hpp @@ -166,6 +166,7 @@ constexpr inline bool is_optional = is_optional_impl>, typename = std::enable_if_t>::value_type>>> @@ -201,6 +202,7 @@ template * * @return Return the result of invoking the function on val */ +// NOLINTNEXTLINE(modernize-use-constraints): library targets C++17 (no requires clauses) template >> [[nodiscard]] constexpr auto operator|(T&& val, Fn&& fn) -> typename std::enable_if_t, std::invoke_result_t> { From 4eb3c59e685409a51b56de374ce0b4969c811a50 Mon Sep 17 00:00:00 2001 From: Christoph Froehlich Date: Sun, 14 Jun 2026 19:38:33 +0000 Subject: [PATCH 03/12] Fix braces --- tests/queue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/queue.cpp b/tests/queue.cpp index 75f7cb8..68c685d 100644 --- a/tests/queue.cpp +++ b/tests/queue.cpp @@ -89,7 +89,7 @@ TEST_CASE("rsl::Queue") { for (auto& producer : producers) producer.join(); for (auto& consumer : consumers) consumer.join(); - CHECK(queue.size() == thread_count * item_count - items_removed); + CHECK(queue.size() == (thread_count * item_count) - items_removed); } } } From 8c77e4cb847e8234a6e171a061bf675037ea32f2 Mon Sep 17 00:00:00 2001 From: Christoph Froehlich Date: Sun, 14 Jun 2026 19:52:30 +0000 Subject: [PATCH 04/12] Fix modernize-use-scoped-lock --- include/rsl/queue.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/rsl/queue.hpp b/include/rsl/queue.hpp index 613b4bc..9d8ba35 100644 --- a/include/rsl/queue.hpp +++ b/include/rsl/queue.hpp @@ -27,7 +27,7 @@ class Queue { * @return Queue size */ [[nodiscard]] auto size() const noexcept { - auto const lock = std::lock_guard(mutex_); + auto const lock = std::scoped_lock(mutex_); return queue_.size(); } @@ -36,7 +36,7 @@ class Queue { * @return True if the queue is empty, otherwise false */ [[nodiscard]] auto empty() const noexcept { - auto const lock = std::lock_guard(mutex_); + auto const lock = std::scoped_lock(mutex_); return queue_.empty(); } @@ -45,7 +45,7 @@ class Queue { * @param value Data to push into the queue */ void push(T value) noexcept { - auto const lock = std::lock_guard(mutex_); + auto const lock = std::scoped_lock(mutex_); queue_.push(std::move(value)); cv_.notify_one(); } @@ -54,7 +54,7 @@ class Queue { * @brief Clear the queue */ void clear() noexcept { - auto const lock = std::lock_guard(mutex_); + auto const lock = std::scoped_lock(mutex_); // Swap queue with an empty queue of the same type to ensure queue_ is left in a // default-constructed state From 784afa9c221fc45f1e8420149db4ec6f5b0acb61 Mon Sep 17 00:00:00 2001 From: Christoph Froehlich Date: Mon, 15 Jun 2026 21:52:16 +0000 Subject: [PATCH 05/12] Use std::numbers if available --- src/random.cpp | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/random.cpp b/src/random.cpp index d613740..7676fd5 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -3,11 +3,26 @@ #include #include #include +#if __has_include() +#include +#endif #include #include namespace rsl { +namespace { + +constexpr auto kPiFallback = 3.141592653589793238462643383279502884; + +#if defined(__cpp_lib_math_constants) && (__cpp_lib_math_constants >= 201907L) +constexpr auto kPi = std::numbers::pi_v; +#else +constexpr auto kPi = kPiFallback; +#endif + +} // namespace + auto rng(std::seed_seq seed_sequence) -> std::mt19937& { thread_local auto generator = std::optional(); @@ -30,14 +45,12 @@ auto rng(std::seed_seq seed_sequence) -> std::mt19937& { } auto random_unit_quaternion() -> Eigen::Quaterniond { - static constexpr auto pi = 3.1415926535897932385; - // From "Uniform Random Rotations", Ken Shoemake, Graphics Gems III, pg. 124-132 auto const x0 = uniform_real(0., 1.); auto const r1 = std::sqrt(1 - x0); auto const r2 = std::sqrt(x0); - auto const t1 = uniform_real(0., 2 * pi); - auto const t2 = uniform_real(0., 2 * pi); + auto const t1 = uniform_real(0., 2 * kPi); + auto const t2 = uniform_real(0., 2 * kPi); auto const x = r1 * std::sin(t1); auto const y = r1 * std::cos(t1); auto const z = r2 * std::sin(t2); From 37c9a25dbb09a67beb884de93028d0a43b09cb69 Mon Sep 17 00:00:00 2001 From: Christoph Froehlich Date: Mon, 15 Jun 2026 21:54:38 +0000 Subject: [PATCH 06/12] Fix modernize-use-constraints for c++20 --- include/rsl/monad.hpp | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/include/rsl/monad.hpp b/include/rsl/monad.hpp index c0531de..2d1426f 100644 --- a/include/rsl/monad.hpp +++ b/include/rsl/monad.hpp @@ -3,6 +3,9 @@ #include +#if defined(__cpp_concepts) && (__cpp_concepts >= 201907L) +#include +#endif #include namespace rsl { @@ -166,13 +169,21 @@ constexpr inline bool is_optional = is_optional_impl= 201907L) +template + requires(rsl::is_optional && + std::invocable>::value_type>) +[[nodiscard]] constexpr auto operator|(T&& opt, Fn&& fn) { + return rsl::mbind(std::forward(opt), std::forward(fn)); +} +#else template >, typename = std::enable_if_t>::value_type>>> [[nodiscard]] constexpr auto operator|(T&& opt, Fn&& fn) { return rsl::mbind(std::forward(opt), std::forward(fn)); } +#endif /** * @brief Overload of the | operator as bind @@ -202,11 +213,18 @@ template * * @return Return the result of invoking the function on val */ -// NOLINTNEXTLINE(modernize-use-constraints): library targets C++17 (no requires clauses) +#if defined(__cpp_concepts) && (__cpp_concepts >= 201907L) +template + requires(!rsl::is_optional && std::invocable) +[[nodiscard]] constexpr auto operator|(T&& val, Fn&& fn) -> std::invoke_result_t { + return std::invoke(std::forward(fn), std::forward(val)); +} +#else template >> [[nodiscard]] constexpr auto operator|(T&& val, Fn&& fn) -> typename std::enable_if_t, std::invoke_result_t> { return std::invoke(std::forward(fn), std::forward(val)); } +#endif #endif // RSL_MONAD_HPP_ From ba579cce10d7585f8e56749cab3d30c5ecb0b89d Mon Sep 17 00:00:00 2001 From: Christoph Froehlich Date: Mon, 15 Jun 2026 22:01:52 +0000 Subject: [PATCH 07/12] Let the pre-commit job run on explicit ubuntu versions --- .github/workflows/format.yaml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/format.yaml b/.github/workflows/format.yaml index c45654b..e5f8186 100644 --- a/.github/workflows/format.yaml +++ b/.github/workflows/format.yaml @@ -9,8 +9,11 @@ on: jobs: pre-commit: - name: pre-commit - runs-on: ubuntu-latest + name: pre-commit (${{ matrix.ubuntu-version }}) + runs-on: ubuntu-${{ matrix.ubuntu-version }} + strategy: + matrix: + ubuntu-version: [24.04, 26.04] steps: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 From b1f6d5059dfe458be8cb21167fcd720ad8b9620c Mon Sep 17 00:00:00 2001 From: Christoph Froehlich Date: Mon, 15 Jun 2026 22:03:43 +0000 Subject: [PATCH 08/12] Fix code style --- src/random.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/random.cpp b/src/random.cpp index 7676fd5..c7a73e4 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -13,12 +13,12 @@ namespace rsl { namespace { -constexpr auto kPiFallback = 3.141592653589793238462643383279502884; +constexpr auto k_pi_fallback = 3.141592653589793238462643383279502884; #if defined(__cpp_lib_math_constants) && (__cpp_lib_math_constants >= 201907L) -constexpr auto kPi = std::numbers::pi_v; +constexpr auto k_pi = std::numbers::pi_v; #else -constexpr auto kPi = kPiFallback; +constexpr auto k_pi = k_pi_fallback; #endif } // namespace @@ -49,8 +49,8 @@ auto random_unit_quaternion() -> Eigen::Quaterniond { auto const x0 = uniform_real(0., 1.); auto const r1 = std::sqrt(1 - x0); auto const r2 = std::sqrt(x0); - auto const t1 = uniform_real(0., 2 * kPi); - auto const t2 = uniform_real(0., 2 * kPi); + auto const t1 = uniform_real(0., 2 * k_pi); + auto const t2 = uniform_real(0., 2 * k_pi); auto const x = r1 * std::sin(t1); auto const y = r1 * std::cos(t1); auto const z = r2 * std::sin(t2); From 706257396c98ca4b99a9ee6dbb7988292715909c Mon Sep 17 00:00:00 2001 From: Christoph Froehlich Date: Mon, 15 Jun 2026 22:10:28 +0000 Subject: [PATCH 09/12] Dont fail-fast --- .github/workflows/format.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/format.yaml b/.github/workflows/format.yaml index e5f8186..e955b25 100644 --- a/.github/workflows/format.yaml +++ b/.github/workflows/format.yaml @@ -12,6 +12,7 @@ jobs: name: pre-commit (${{ matrix.ubuntu-version }}) runs-on: ubuntu-${{ matrix.ubuntu-version }} strategy: + fail-fast: false matrix: ubuntu-version: [24.04, 26.04] steps: From e3af597ded65bee8dcfa0505b25781ad4f97ea33 Mon Sep 17 00:00:00 2001 From: Christoph Froehlich Date: Mon, 15 Jun 2026 22:11:27 +0000 Subject: [PATCH 10/12] Fix std::numbers again --- src/random.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/random.cpp b/src/random.cpp index c7a73e4..032d46c 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -13,12 +13,10 @@ namespace rsl { namespace { -constexpr auto k_pi_fallback = 3.141592653589793238462643383279502884; - #if defined(__cpp_lib_math_constants) && (__cpp_lib_math_constants >= 201907L) constexpr auto k_pi = std::numbers::pi_v; #else -constexpr auto k_pi = k_pi_fallback; +constexpr auto k_pi = 3.141592653589793238462643383279502884; #endif } // namespace From cbf6a54efd55554c3cc98173331a90dddcaf1260 Mon Sep 17 00:00:00 2001 From: Christoph Froehlich Date: Tue, 16 Jun 2026 06:39:24 +0000 Subject: [PATCH 11/12] Fix constrained overloads for MSVC --- include/rsl/monad.hpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/include/rsl/monad.hpp b/include/rsl/monad.hpp index 2d1426f..9135aa2 100644 --- a/include/rsl/monad.hpp +++ b/include/rsl/monad.hpp @@ -171,8 +171,7 @@ constexpr inline bool is_optional = is_optional_impl= 201907L) template - requires(rsl::is_optional && - std::invocable>::value_type>) + requires(rsl::is_optional) [[nodiscard]] constexpr auto operator|(T&& opt, Fn&& fn) { return rsl::mbind(std::forward(opt), std::forward(fn)); } @@ -215,8 +214,8 @@ template */ #if defined(__cpp_concepts) && (__cpp_concepts >= 201907L) template - requires(!rsl::is_optional && std::invocable) -[[nodiscard]] constexpr auto operator|(T&& val, Fn&& fn) -> std::invoke_result_t { + requires(!rsl::is_optional) +[[nodiscard]] constexpr auto operator|(T&& val, Fn&& fn) { return std::invoke(std::forward(fn), std::forward(val)); } #else From 2fa4b9cb67e72dc3646d0b24fcb401a2cdd3c029 Mon Sep 17 00:00:00 2001 From: Christoph Froehlich Date: Tue, 16 Jun 2026 07:35:57 +0000 Subject: [PATCH 12/12] Fix constrained overloads for GCC and MSVC --- include/rsl/monad.hpp | 12 ++++++++++-- tests/monad.cpp | 45 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/include/rsl/monad.hpp b/include/rsl/monad.hpp index 9135aa2..05c7824 100644 --- a/include/rsl/monad.hpp +++ b/include/rsl/monad.hpp @@ -156,6 +156,13 @@ constexpr inline bool is_optional_impl> = true; template constexpr inline bool is_optional = is_optional_impl>>; +template +constexpr inline bool is_expected_impl = false; +template +constexpr inline bool is_expected_impl> = true; +template +constexpr inline bool is_expected = is_expected_impl>>; + } // namespace rsl /** @@ -214,12 +221,13 @@ template */ #if defined(__cpp_concepts) && (__cpp_concepts >= 201907L) template - requires(!rsl::is_optional) + requires(!rsl::is_optional && !rsl::is_expected && std::invocable) [[nodiscard]] constexpr auto operator|(T&& val, Fn&& fn) { return std::invoke(std::forward(fn), std::forward(val)); } #else -template >> +template && !rsl::is_expected>> [[nodiscard]] constexpr auto operator|(T&& val, Fn&& fn) -> typename std::enable_if_t, std::invoke_result_t> { return std::invoke(std::forward(fn), std::forward(val)); diff --git a/tests/monad.cpp b/tests/monad.cpp index f19f73c..8878620 100644 --- a/tests/monad.cpp +++ b/tests/monad.cpp @@ -5,7 +5,10 @@ #include #include +#include #include +#include +#include using namespace std::string_literals; @@ -34,6 +37,22 @@ Result multiply(double x, double y) { return x * y; } Result divide_3(double x) { return divide(3, x); } Result multiply_3(double x) { return multiply(3, x); } + +template +constexpr bool has_pipe_v = false; + +template +constexpr bool + has_pipe_v() | std::declval())>> = true; + +constexpr auto plus_one = [](int x) { return x + 1; }; + +static_assert(has_pipe_v, decltype(+divide_3)>); +static_assert(std::is_same_v>() | divide_3), Result>); +static_assert(has_pipe_v); +static_assert(std::is_same_v() | plus_one), int>); +static_assert(std::is_convertible_v); } // namespace TEST_CASE("rsl::mbind") { @@ -229,6 +248,32 @@ TEST_CASE("operator|") { } } +TEST_CASE("operator| expected overload selection") { + auto const input = Result{5.0}; + + auto const result = input | divide_3; + + REQUIRE(rsl::has_value(result)); + CHECK(result.value() == Catch::Approx(3.0 / 5.0)); +} + +TEST_CASE("operator| expected chaining") { + auto const input = Result{5.0}; + + auto const result = input | divide_3 | multiply_3; + + REQUIRE(rsl::has_value(result)); + CHECK(result.value() == Catch::Approx(3.0 * (3.0 / 5.0))); +} + +TEST_CASE("operator| does not hijack ctype bitmask") { + auto const mask = std::ctype_base::alpha | std::ctype_base::digit | std::ctype_base::punct; + + CHECK((mask & std::ctype_base::alpha) != 0); + CHECK((mask & std::ctype_base::digit) != 0); + CHECK((mask & std::ctype_base::punct) != 0); +} + TEST_CASE("rsl::maybe_error") { SECTION("No error") { CHECK(rsl::maybe_error(tl::expected(42)) == std::nullopt);