diff --git a/.github/workflows/format.yaml b/.github/workflows/format.yaml index c45654b..e955b25 100644 --- a/.github/workflows/format.yaml +++ b/.github/workflows/format.yaml @@ -9,8 +9,12 @@ on: jobs: pre-commit: - name: pre-commit - runs-on: ubuntu-latest + name: pre-commit (${{ matrix.ubuntu-version }}) + runs-on: ubuntu-${{ matrix.ubuntu-version }} + strategy: + fail-fast: false + matrix: + ubuntu-version: [24.04, 26.04] steps: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 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..05c7824 100644 --- a/include/rsl/monad.hpp +++ b/include/rsl/monad.hpp @@ -1,7 +1,11 @@ -#pragma once +#ifndef RSL_MONAD_HPP_ +#define RSL_MONAD_HPP_ #include +#if defined(__cpp_concepts) && (__cpp_concepts >= 201907L) +#include +#endif #include namespace rsl { @@ -20,8 +24,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 +45,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()); } @@ -152,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 /** @@ -165,12 +176,20 @@ constexpr inline bool is_optional = is_optional_impl= 201907L) +template + requires(rsl::is_optional) +[[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 @@ -200,8 +219,19 @@ template * * @return Return the result of invoking the function on val */ -template >> +#if defined(__cpp_concepts) && (__cpp_concepts >= 201907L) +template + 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 && !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)); } +#endif + +#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..9d8ba35 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 @@ -26,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(); } @@ -35,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(); } @@ -44,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(); } @@ -53,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 @@ -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_ diff --git a/src/random.cpp b/src/random.cpp index d613740..032d46c 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -3,11 +3,24 @@ #include #include #include +#if __has_include() +#include +#endif #include #include namespace rsl { +namespace { + +#if defined(__cpp_lib_math_constants) && (__cpp_lib_math_constants >= 201907L) +constexpr auto k_pi = std::numbers::pi_v; +#else +constexpr auto k_pi = 3.141592653589793238462643383279502884; +#endif + +} // namespace + auto rng(std::seed_seq seed_sequence) -> std::mt19937& { thread_local auto generator = std::optional(); @@ -30,14 +43,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 * 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); 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); 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); } } }