├── .clang-format ├── .clang-tidy ├── .codespell_words ├── .github └── workflows │ ├── ci.yaml │ ├── docs.yaml │ └── format.yaml ├── .gitignore ├── .pre-commit-config.yaml ├── CHANGELOG.rst ├── CMakeLists.txt ├── CMakePresets.json ├── LICENSE ├── README.md ├── cmake └── rsl-config.cmake ├── codecov.yml ├── docs └── CMakeLists.txt ├── include └── rsl │ ├── algorithm.hpp │ ├── monad.hpp │ ├── no_discard.hpp │ ├── overload.hpp │ ├── parameter_validators.hpp │ ├── queue.hpp │ ├── random.hpp │ ├── static_string.hpp │ ├── static_vector.hpp │ ├── strong_type.hpp │ └── try.hpp ├── package.xml ├── src ├── parameter_validators.cpp └── random.cpp └── tests ├── CMakeLists.txt ├── FindCatch2.cmake ├── algorithm.cpp ├── install ├── CMakeLists.txt └── install.cpp ├── monad.cpp ├── no_discard.cpp ├── overload.cpp ├── parameter_validators.cpp ├── queue.cpp ├── random.cpp ├── static_string.cpp ├── static_vector.cpp ├── strong_type.cpp └── try.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | 3 | ColumnLimit: 100 4 | IndentWidth: 4 5 | 6 | DerivePointerAlignment: false 7 | QualifierAlignment: Right 8 | ReferenceAlignment: Pointer 9 | SpaceAroundPointerQualifiers: Default 10 | PointerAlignment: Left 11 | 12 | IncludeBlocks: Regroup 13 | IncludeCategories: 14 | - Priority: 1 15 | Regex: '^$' 18 | - Priority: 3 19 | Regex: '^<(.+)>$' 20 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: > 2 | -*, 3 | bugprone-*, 4 | clang-analyzer-*, 5 | concurrency-*, 6 | cppcoreguidelines-*, 7 | misc-*, 8 | modernize-*, 9 | performance-*, 10 | portability-*, 11 | readability-*, 12 | -bugprone-unchecked-optional-access, 13 | -concurrency-mt-unsafe, 14 | -cppcoreguidelines-avoid-magic-numbers, 15 | -cppcoreguidelines-macro-usage, 16 | -cppcoreguidelines-pro-bounds-pointer-arithmetic, 17 | -misc-include-cleaner, 18 | -misc-non-private-member-variables-in-classes, 19 | -modernize-avoid-bind, 20 | -modernize-use-trailing-return-type, 21 | -readability-avoid-unconditional-preprocessor-if, 22 | -readability-braces-around-statements, 23 | -readability-identifier-length, 24 | -readability-magic-numbers, 25 | -readability-uppercase-literal-suffix, 26 | CheckOptions: 27 | - { key: readability-identifier-naming.ClassCase, value: CamelCase } 28 | - { key: readability-identifier-naming.EnumCase, value: CamelCase } 29 | - { key: readability-identifier-naming.TypeAliasCase, value: CamelCase } 30 | - { key: readability-identifier-naming.NamespaceCase, value: lower_case } 31 | - { key: readability-identifier-naming.FunctionCase, value: lower_case } 32 | - { key: readability-identifier-naming.VariableCase, value: lower_case } 33 | - { key: readability-identifier-naming.ParameterCase, value: lower_case } 34 | - { key: readability-identifier-naming.MemberCase, value: lower_case } 35 | - { key: readability-identifier-naming.PrivateMemberCase, value: lower_case } 36 | - { key: readability-identifier-naming.PrivateMemberSuffix, value: _ } 37 | - { key: readability-function-cognitive-complexity.Threshold, value: 15 } 38 | - { key: readability-function-cognitive-complexity.IgnoreMacros, value: True } 39 | HeaderFilterRegex: '.*' 40 | WarningsAsErrors: '*' 41 | -------------------------------------------------------------------------------- /.codespell_words: -------------------------------------------------------------------------------- 1 | unexpect 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | push: 7 | 8 | jobs: 9 | cmake: 10 | name: cmake (${{ matrix.ros_distro }}, ${{ matrix.preset }}, ${{ matrix.compiler.name }}) 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | ros_distro: [humble, rolling] 15 | preset: [debug, release, asan, codecov] 16 | compiler: 17 | - { name: GCC } 18 | - { name: Clang, flags: -DCMAKE_CXX_COMPILER=clang++ } 19 | exclude: 20 | - preset: codecov 21 | compiler: { name: Clang } 22 | - preset: codecov 23 | ros_distro: humble 24 | runs-on: ubuntu-22.04 25 | container: ghcr.io/picknikrobotics/rsl:upstream-${{ matrix.ros_distro }} 26 | env: 27 | CCACHE_DIR: ${{ github.workspace }}/.ccache 28 | steps: 29 | - name: Check out the repo 30 | uses: actions/checkout@v4 31 | - name: Cache ccache 32 | uses: actions/cache@v4 33 | with: 34 | path: ${{ env.CCACHE_DIR }} 35 | key: ccache-cmake-${{ matrix.ros_distro }}-${{ matrix.preset }}-${{ github.sha }}-${{ github.run_id }} 36 | restore-keys: | 37 | ccache-cmake-${{ matrix.ros_distro }}-${{ matrix.preset }}-${{ github.sha }} 38 | ccache-cmake-${{ matrix.ros_distro }}-${{ matrix.preset }} 39 | - name: Source ROS 40 | run: | 41 | . /opt/ros/${{ matrix.ros_distro }}/setup.sh 42 | echo "$(env)" >> $GITHUB_ENV 43 | - name: Install Clang 44 | if: matrix.compiler.name == 'clang' 45 | run: sudo apt update && sudo apt install clang 46 | - name: Configure 47 | run: cmake --preset ${{ matrix.preset }} ${{ matrix.compiler.flags }} -DCMAKE_INSTALL_PREFIX=install -DCMAKE_VERBOSE_MAKEFILE=ON 48 | - name: Build 49 | run: cmake --build build/${{ matrix.preset }} --target install 50 | - name: Test 51 | run: ctest --test-dir build/${{ matrix.preset }} --rerun-failed --output-on-failure 52 | - name: Build Doxygen Site 53 | run: cmake --build build/${{ matrix.preset }} --target docs 54 | - name: Analyze 55 | run: cmake --build build/${{ matrix.preset }} --target tidy 56 | - name: Generate LCOV report 57 | if: ${{ matrix.preset == 'codecov' }} 58 | run: lcov -c -d . -o coverage.info --no-external --exclude "*/build/**" --ignore-errors mismatch 59 | - uses: codecov/codecov-action@v3 60 | if: ${{ matrix.preset == 'codecov' }} 61 | with: 62 | files: ./coverage.info 63 | name: codecov-umbrella 64 | flags: ${{ matrix.ros_distro }} 65 | 66 | ros: 67 | strategy: 68 | matrix: 69 | ros_distro: [humble, rolling] 70 | runs-on: ubuntu-22.04 71 | container: ghcr.io/picknikrobotics/rsl:upstream-${{ matrix.ros_distro }} 72 | env: 73 | CCACHE_DIR: ${{ github.workspace }}/.ccache 74 | steps: 75 | - name: Check out the repo 76 | uses: actions/checkout@v4 77 | - name: Cache ccache 78 | uses: actions/cache@v4 79 | with: 80 | path: ${{ env.CCACHE_DIR }} 81 | key: ccache-ros-${{ matrix.ros_distro }}-${{ github.sha }}-${{ github.run_id }} 82 | restore-keys: | 83 | ccache-ros-${{ matrix.ros_distro }}-${{ github.sha }} 84 | ccache-ros-${{ matrix.ros_distro }} 85 | - uses: ros-tooling/action-ros-ci@v0.3 86 | with: 87 | package-name: rsl 88 | target-ros2-distro: ${{ matrix.ros_distro }} 89 | colcon-defaults: | 90 | { 91 | "build": { 92 | "mixin": ["ccache", "lld"] 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /.github/workflows/docs.yaml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | 9 | permissions: 10 | contents: read 11 | pages: write 12 | id-token: write 13 | 14 | jobs: 15 | docs: 16 | environment: 17 | name: github-pages 18 | url: ${{ steps.deployment.outputs.page_url }} 19 | runs-on: ubuntu-latest 20 | container: ghcr.io/picknikrobotics/rsl:upstream-rolling 21 | steps: 22 | - name: Check out the repo 23 | uses: actions/checkout@v4 24 | - name: Setup Pages 25 | uses: actions/configure-pages@v5 26 | - name: Source ROS 27 | run: | 28 | . /opt/ros/rolling/setup.sh 29 | echo "$(env)" >> $GITHUB_ENV 30 | - name: Configure 31 | run: cmake -B build 32 | - name: Build Doxygen Site 33 | run: cmake --build build --target docs 34 | - name: Upload artifact 35 | uses: actions/upload-pages-artifact@v3 36 | with: 37 | path: build/docs/html 38 | - name: Deploy to GitHub Pages 39 | id: deployment 40 | uses: actions/deploy-pages@v4 41 | -------------------------------------------------------------------------------- /.github/workflows/format.yaml: -------------------------------------------------------------------------------- 1 | name: Format 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | push: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | pre-commit: 12 | name: pre-commit 13 | runs-on: ubuntu-22.04 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-python@v5 17 | - name: Install clang-format-14 18 | run: sudo apt-get install clang-format-14 19 | - uses: pre-commit/action@v3.0.1 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | # Standard hooks 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v5.0.0 5 | hooks: 6 | - id: check-added-large-files 7 | - id: check-ast 8 | - id: check-builtin-literals 9 | - id: check-byte-order-marker 10 | - id: check-case-conflict 11 | - id: check-docstring-first 12 | - id: check-executables-have-shebangs 13 | - id: check-json 14 | - id: check-merge-conflict 15 | - id: check-symlinks 16 | - id: check-toml 17 | - id: check-vcs-permalinks 18 | - id: check-yaml 19 | - id: debug-statements 20 | - id: destroyed-symlinks 21 | - id: detect-private-key 22 | - id: end-of-file-fixer 23 | - id: fix-byte-order-marker 24 | - id: fix-encoding-pragma 25 | - id: forbid-new-submodules 26 | - id: mixed-line-ending 27 | - id: name-tests-test 28 | - id: requirements-txt-fixer 29 | - id: sort-simple-yaml 30 | - id: trailing-whitespace 31 | 32 | - repo: local 33 | hooks: 34 | - id: clang-format 35 | name: clang-format 36 | description: Format files with ClangFormat 14. 37 | entry: clang-format-14 38 | language: system 39 | files: \.(c|cc|cxx|cpp|frag|glsl|h|hpp|hxx|ih|ispc|ipp|java|js|m|proto|vert)$ 40 | args: ['-fallback-style=none', '-i'] 41 | 42 | - repo: https://github.com/codespell-project/codespell 43 | rev: v2.3.0 44 | hooks: 45 | - id: codespell 46 | args: ['--write-changes', '--ignore-words=.codespell_words'] 47 | exclude: CHANGELOG.rst 48 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | ^^^^^^^^^^^^^^^^^^^^^^^^^ 2 | Changelog for package rsl 3 | ^^^^^^^^^^^^^^^^^^^^^^^^^ 4 | 5 | 1.2.0 (2025-06-11) 6 | ------------------ 7 | * Remove devcontainer 8 | * Fix new clang-tidy warnings 9 | * Update Catch2 10 | * Ensure FindEigen3.cmake is not used (`#126 `_) 11 | * Eigen: Add NO_MODULE to find_package (`#125 `_) 12 | Work around RHEL 8 failure due to a faulty FindEigen3.cmake module being found 13 | * Hide symbols from shared libraries by default and support DLL creation 14 | * Fix static_string.cpp compilation with MSVC standard library 15 | std::array's iterator is a pointer in libc++ and libstdc++ so 16 | `const auto*` works fine. However the array iterator is not a 17 | pointer on MSVC so compilation fails. Removing the asterisk still 18 | results in a pointer type being used but causes a clang-tidy check 19 | to fail so we have to disable that. 20 | * Disable TRY macro tests on MSVC 21 | * Disable compiler warnings by default 22 | Unconditionally enabling -Werror is a heavy-handed approach and not 23 | ideal when shipping code to be used by many third parties. In fact 24 | it's even better to disable warnings by default since compiler 25 | warnings are not hard requirements. They're merely a development 26 | tool that developers should opt into. 27 | * Contributors: Chris Thrasher, Christoph Fröhlich, Griswald Brooks, mosfet80 28 | 29 | 1.1.0 (2023-12-20) 30 | ------------------ 31 | * Fix `ament_target_dependencies` integration 32 | * Add `rsl::maybe_error` 33 | * Disable tests by default 34 | * Stop using non-standard `M_PI` 35 | * Contributors: Chris Thrasher 36 | 37 | 1.0.1 (2023-12-06) 38 | ------------------ 39 | * Add more messages for assertion failures 40 | * Document API changes from v0 to v1 41 | * Contributors: Chris Thrasher 42 | 43 | 1.0.0 (2023-12-04) 44 | ------------------ 45 | * Add `rsl::StrongType` 46 | * Quote values in error messages 47 | * Make doxygen a weak dependency 48 | * Mark git as a test dependency 49 | * Contributors: Andrew, Chris Thrasher, Michael Carroll, Tyler Weaver, andrea 50 | 51 | 0.2.2 (2023-03-23) 52 | ------------------ 53 | * Deprecate `rsl::lower_bounds` and `rsl::upper_bounds` 54 | * Depend on ament_cmake_ros for SHARED default 55 | * Upgrade with new pkgs to fix issue with ROS 56 | * Update Catch2 57 | * Contributors: Chris Thrasher, Tyler Weaver 58 | 59 | 0.2.1 (2022-11-29) 60 | ------------------ 61 | * Use constructors over factory functions when possible 62 | * Implement `rsl::rng` with `std::optional` 63 | * Fix bug when trying to seed rng from first call 64 | * Make `rsl::rng()` tests more strenuous 65 | Seeding on the first call tests a code path that hadn't yet been 66 | tested but is a valid use of the API. 67 | * Change version of range-v3 to allow building on Ubuntu Focal (`#73 `_) 68 | * Add missing header 69 | Fixes a build issue when using GCC 12 70 | * Make it easy for users to override 71 | * Update Catch2 72 | * Add tests for `rsl::to_parameter_result_msg` 73 | * Contributors: Chris Thrasher, Tony Najjar, Tyler Weaver 74 | 75 | 0.2.0 (2022-11-15) 76 | ------------------ 77 | * New features 78 | * rclcpp::Parameter validator functions 79 | * Algorithms from parameter_traits 80 | * `rsl::StaticVector` and `rsl::StaticString` 81 | * Doxygen site: https://picknikrobotics.github.io/RSL/files.html 82 | * Link to the docs from the README 83 | * Use doxygen-awesome documentation style 84 | * Deploy documentation website from CI 85 | * Add Doxygen comments for `rsl::Overload` 86 | * Add Doxygen comments for `TRY` 87 | * Cleanups 88 | * Prevent unnecessary string copy 89 | * Ensure global constants do not violate the ODR 90 | * Hide `rsl::NoDiscard` implementation details 91 | * Use more `[[nodiscard]]` 92 | * Fix to_vector for vector 93 | * Ensure static container iterators are used 94 | * Default to building a shared library 95 | * Use generator-agnostic CMake commands 96 | * Remove C compiler settings 97 | * Don't require compiler extensions 98 | * Use more expressive path variable 99 | * Add clang-tidy integration and fix errors 100 | * Address clang-tidy findings 101 | * Update FindCatch2 module 102 | * Update Catch2 103 | * Contributors: Chris Thrasher, Tyler Weaver 104 | 105 | 0.1.1 (2022-10-04) 106 | ------------------ 107 | * Fix CMake configuration warnings 108 | * Contributors: Chris Thrasher 109 | 110 | 0.1.0 (2022-10-04) 111 | ------------------ 112 | * monad.hpp - Functions and operators for monadic expressions 113 | * no_discard.hpp - `[[nodiscard]]` for lambdas 114 | * overload.hpp - Class template for easily visiting variants 115 | * queue.hpp - Thread-safe queue 116 | * random.hpp - Modern C++ randomness made easy 117 | * try.hpp - Macro to emulatate absl::CONFIRM or operator? from Rust 118 | * Contributors: Chris Thrasher, Tyler Weaver 119 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | project(rsl VERSION 1.1.0 LANGUAGES CXX DESCRIPTION "ROS Support Library") 3 | 4 | set(CMAKE_CXX_EXTENSIONS OFF) 5 | 6 | find_package(Eigen3 REQUIRED CONFIG) 7 | find_package(fmt REQUIRED) 8 | find_package(rclcpp REQUIRED) 9 | find_package(tcb_span REQUIRED) 10 | find_package(tl_expected REQUIRED) 11 | 12 | option(RSL_ENABLE_WARNINGS "Enable compiler warnings" OFF) 13 | if(RSL_ENABLE_WARNINGS AND CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)") 14 | add_compile_options(-Werror -Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wsign-conversion -Wold-style-cast) 15 | endif() 16 | 17 | option(BUILD_SHARED_LIBS "Build shared libraries" ON) 18 | 19 | include(GenerateExportHeader) 20 | add_library(rsl 21 | src/parameter_validators.cpp 22 | src/random.cpp 23 | ) 24 | add_library(rsl::rsl ALIAS rsl) 25 | target_compile_features(rsl PUBLIC cxx_std_17) 26 | target_include_directories(rsl PUBLIC 27 | $ 28 | $ 29 | ) 30 | target_link_libraries(rsl PUBLIC 31 | Eigen3::Eigen 32 | fmt::fmt 33 | rclcpp::rclcpp 34 | tcb_span::tcb_span 35 | tl_expected::tl_expected 36 | ) 37 | set_target_properties(rsl PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN YES) 38 | generate_export_header(rsl EXPORT_FILE_NAME include/rsl/export.hpp) 39 | 40 | add_subdirectory(docs) 41 | 42 | option(RSL_BUILD_TESTING "Build tests" OFF) 43 | if(RSL_BUILD_TESTING) 44 | add_subdirectory(tests) 45 | endif() 46 | 47 | include(CMakePackageConfigHelpers) 48 | 49 | install( 50 | DIRECTORY include/ ${PROJECT_BINARY_DIR}/include/ 51 | DESTINATION include/rsl 52 | ) 53 | install( 54 | TARGETS rsl EXPORT rsl-targets 55 | ARCHIVE DESTINATION lib 56 | LIBRARY DESTINATION lib 57 | RUNTIME DESTINATION bin 58 | INCLUDES DESTINATION include/rsl 59 | ) 60 | install( 61 | EXPORT rsl-targets 62 | NAMESPACE rsl:: 63 | DESTINATION share/rsl/cmake 64 | ) 65 | write_basic_package_version_file(rsl-config-version.cmake COMPATIBILITY SameMajorVersion) 66 | install( 67 | FILES cmake/rsl-config.cmake ${PROJECT_BINARY_DIR}/rsl-config-version.cmake 68 | DESTINATION share/rsl/cmake 69 | ) 70 | 71 | add_custom_target(tidy COMMAND run-clang-tidy -p ${CMAKE_BINARY_DIR}) 72 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "cmakeMinimumRequired": { 4 | "major": 3, 5 | "minor": 22, 6 | "patch": 0 7 | }, 8 | "configurePresets": [ 9 | { 10 | "name": "relwithdebinfo", 11 | "displayName": "RelWithDebInfo", 12 | "generator": "Ninja", 13 | "binaryDir": "${sourceDir}/build/relwithdebinfo", 14 | "cacheVariables": { 15 | "CMAKE_BUILD_TYPE": "RelWithDebInfo", 16 | "CMAKE_CXX_COMPILER_LAUNCHER": "ccache", 17 | "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", 18 | "CMAKE_EXE_LINKER_FLAGS": "-fuse-ld=lld", 19 | "CMAKE_MODULE_LINKER_FLAGS": "-fuse-ld=lld", 20 | "CMAKE_SHARED_LINKER_FLAGS": "-fuse-ld=lld", 21 | "RSL_BUILD_TESTING": "ON", 22 | "RSL_ENABLE_WARNINGS": "ON" 23 | }, 24 | "warnings": { 25 | "unusedCli": false 26 | } 27 | }, 28 | { 29 | "name": "debug", 30 | "inherits": "relwithdebinfo", 31 | "displayName": "Debug", 32 | "binaryDir": "${sourceDir}/build/debug", 33 | "cacheVariables": { 34 | "CMAKE_BUILD_TYPE": "Debug" 35 | } 36 | }, 37 | { 38 | "name": "release", 39 | "inherits": "relwithdebinfo", 40 | "displayName": "Release", 41 | "binaryDir": "${sourceDir}/build/release", 42 | "cacheVariables": { 43 | "CMAKE_BUILD_TYPE": "Release" 44 | } 45 | }, 46 | { 47 | "name": "asan", 48 | "inherits": "debug", 49 | "displayName": "Address sanitizer debug", 50 | "binaryDir": "${sourceDir}/build/asan", 51 | "cacheVariables": { 52 | "CMAKE_CXX_FLAGS": "-fsanitize=address" 53 | } 54 | }, 55 | { 56 | "name": "codecov", 57 | "inherits": "debug", 58 | "displayName": "Code coverage reporting", 59 | "binaryDir": "${sourceDir}/build/codecov", 60 | "cacheVariables": { 61 | "CMAKE_CXX_FLAGS": "--coverage" 62 | } 63 | } 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Redistribution and use in source and binary forms, with or without 2 | modification, are permitted provided that the following conditions are met: 3 | 4 | * Redistributions of source code must retain the above copyright 5 | notice, this list of conditions and the following disclaimer. 6 | 7 | * Redistributions in binary form must reproduce the above copyright 8 | notice, this list of conditions and the following disclaimer in the 9 | documentation and/or other materials provided with the distribution. 10 | 11 | * Neither the name of the copyright holder nor the names of its 12 | contributors may be used to endorse or promote products derived from 13 | this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ROS Support Library (RSL) 2 | 3 | ![GitHub CI Workflow Status](https://img.shields.io/github/actions/workflow/status/picknikrobotics/RSL/.github/workflows/ci.yaml?branch=main&label=CI) 4 | [![codecov](https://codecov.io/github/PickNikRobotics/RSL/branch/main/graph/badge.svg?token=t85cTyvsez)](https://codecov.io/github/PickNikRobotics/RSL) 5 | ![GitHub](https://img.shields.io/github/license/PickNikRobotics/RSL) 6 | 7 | RSL is a collection of C++17 utilities for ROS projects. 8 | 9 | Read the docs [here](https://picknikrobotics.github.io/RSL/files.html). 10 | 11 | ## Killer Features 12 | 13 | * [algorithm](include/rsl/algorithm.hpp) - Functions for inspecting collections 14 | * [monad.hpp](include/rsl/monad.hpp) - Functions and operators for monadic expressions 15 | * [no_discard.hpp](include/rsl/no_discard.hpp) - `[[nodiscard]]` for lambdas 16 | * [overload.hpp](include/rsl/overload.hpp) - Class template for easily visiting variants 17 | * [parameter_validators.hpp](include/rsl/parameter_validators.hpp) - Functions for validating rclcpp::Parameter 18 | * [queue.hpp](include/rsl/queue.hpp) - Thread-safe queue 19 | * [random.hpp](include/rsl/random.hpp) - Modern C++ randomness made easy 20 | * [static_string.hpp](include/rsl/static_string.hpp) - Static capacity string class 21 | * [static_vector.hpp](include/rsl/static_vector.hpp) - Static capacity vector class 22 | * [strong_type.hpp](include/rsl/strong_type.hpp) - Strong typedef class 23 | * [try.hpp](include/rsl/try.hpp) - Macro to emulatate absl::CONFIRM or operator? from Rust 24 | -------------------------------------------------------------------------------- /cmake/rsl-config.cmake: -------------------------------------------------------------------------------- 1 | include(CMakeFindDependencyMacro) 2 | 3 | find_dependency(Eigen3 CONFIG) 4 | find_dependency(fmt) 5 | find_dependency(rclcpp) 6 | find_dependency(tcb_span) 7 | find_dependency(tl_expected) 8 | 9 | include(${CMAKE_CURRENT_LIST_DIR}/rsl-targets.cmake) 10 | 11 | # Support ament_target_dependencies 12 | set(rsl_TARGETS rsl::rsl) 13 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "tests/*" 3 | -------------------------------------------------------------------------------- /docs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Doxygen 1.8 COMPONENTS doxygen) 2 | 3 | if(NOT Doxygen_FOUND) 4 | return() 5 | endif() 6 | 7 | file(DOWNLOAD 8 | https://raw.githubusercontent.com/jothepro/doxygen-awesome-css/v2.1.0/doxygen-awesome.css 9 | ${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome.css) 10 | 11 | set(DOXYGEN_DISABLE_INDEX NO) 12 | set(DOXYGEN_FULL_SIDEBAR NO) 13 | set(DOXYGEN_GENERATE_TREEVIEW YES) 14 | set(DOXYGEN_HTML_EXTRA_STYLESHEET ${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome.css) 15 | set(DOXYGEN_STRIP_FROM_PATH ${PROJECT_SOURCE_DIR}/include) 16 | set(DOXYGEN_WARN_AS_ERROR YES) 17 | set(DOXYGEN_WARN_IF_UNDOCUMENTED NO) 18 | doxygen_add_docs(docs ${PROJECT_SOURCE_DIR}/include) 19 | -------------------------------------------------------------------------------- /include/rsl/algorithm.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace rsl { 6 | 7 | /** @file */ 8 | 9 | /** 10 | * @brief Determine if a collection contains a value. Example usage: 11 | * 12 | * @code 13 | * auto values = std::vector{1, 2, 3}; 14 | * rsl::contains(values, 3); // true 15 | * rsl::contains(values, -1); // false 16 | * @endcode 17 | */ 18 | template 19 | [[nodiscard]] auto contains(Collection const& collection, 20 | typename Collection::const_reference value) { 21 | return std::find(collection.cbegin(), collection.cend(), value) != collection.cend(); 22 | } 23 | 24 | /** 25 | * @brief Determine if all elements in a collection are unique. Example usage: 26 | * 27 | * @code 28 | * rsl::is_unique(std::vector{2,2}); // false 29 | * rsl::is_unique(std::vector{1,2,3}); // true 30 | * @endcode 31 | * 32 | * Because of how this function is implemented, the collection parameter is 33 | * taken by value, creating a copy. The implication of this is that it will not work 34 | * on collections of non-copy-able objects. 35 | */ 36 | template 37 | [[nodiscard]] auto is_unique(Collection collection) { 38 | std::sort(collection.begin(), collection.end()); 39 | return std::adjacent_find(collection.cbegin(), collection.cend()) == collection.cend(); 40 | } 41 | 42 | } // namespace rsl 43 | -------------------------------------------------------------------------------- /include/rsl/monad.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace rsl { 8 | 9 | /** @file */ 10 | 11 | /** 12 | * @brief Monad optional bind 13 | * 14 | * @param opt Input optional 15 | * @param fn Function, must return a optional 16 | * 17 | * @tparam T Input type 18 | * @tparam Fn Function 19 | * 20 | * @return Return type of fn 21 | */ 22 | template 23 | [[nodiscard]] constexpr auto mbind(std::optional const& opt, Fn fn) 24 | -> std::invoke_result_t { 25 | static_assert(std::is_convertible_v>, 26 | "Fn must return a std::optional"); 27 | if (opt) return fn(opt.value()); 28 | return std::invoke_result_t{std::nullopt}; 29 | } 30 | 31 | /** 32 | * @brief Monad tl::expected 33 | * 34 | * @param exp tl::expected input 35 | * @param fn Function to apply 36 | * 37 | * @tparam T Type for the input expected 38 | * @tparam E Error type 39 | * @tparam Fn Function 40 | * 41 | * @return Return type of the function 42 | */ 43 | template 44 | [[nodiscard]] constexpr auto mbind(tl::expected const& exp, Fn fn) 45 | -> std::invoke_result_t { 46 | if (exp) return fn(exp.value()); 47 | return tl::unexpected(exp.error()); 48 | } 49 | 50 | /** 51 | * @brief Monadic try, used to lift a function that throws an exception into one that returns an 52 | * tl::expected 53 | * 54 | * @param fn Function to call 55 | * 56 | * @tparam Fn Function type 57 | * 58 | * @return Return value of the function 59 | */ 60 | template 61 | [[nodiscard]] auto mtry(Fn fn) -> tl::expected, std::exception_ptr> try { 62 | return fn(); 63 | } catch (...) { 64 | return tl::unexpected(std::current_exception()); 65 | } 66 | 67 | /** 68 | * @brief Monadic compose two monad functions 69 | * 70 | * @param fn First function 71 | * @param g Second function 72 | * 73 | * @tparam Fn Type of the first function 74 | * @tparam G Type of the second function 75 | * 76 | * @return A functional composition of two monad functions 77 | */ 78 | template 79 | [[nodiscard]] constexpr auto mcompose(Fn fn, G g) { 80 | return [=](auto value) { return mbind(fn(value), g); }; 81 | } 82 | 83 | /** 84 | * @brief Variadic mcompose 85 | * 86 | * @param t First function 87 | * @param g Second function 88 | * @param vars Rest of the functions 89 | * 90 | * @tparam T Type of the first function 91 | * @tparam G Type of the second function 92 | * @tparam Ts Types of the rest of the functions 93 | * 94 | * @return A functional composition of variadic monad functions 95 | */ 96 | template 97 | [[nodiscard]] constexpr auto mcompose(T t, G g, Ts... vars) { 98 | auto exp = mcompose(t, g); 99 | return mcompose(exp, vars...); 100 | } 101 | 102 | /** 103 | * @brief Test if expected type is Error 104 | * 105 | * @param exp Input tl::expected value 106 | * 107 | * @return True if expected paraemter is Error 108 | */ 109 | template 110 | [[nodiscard]] constexpr auto has_error(tl::expected const& exp) { 111 | return !exp.has_value(); 112 | } 113 | 114 | /** 115 | * @brief Test if expected type is Value 116 | * 117 | * @param exp Input tl::expected value 118 | * 119 | * @return True if expected paraemter is Value 120 | */ 121 | template 122 | [[nodiscard]] constexpr auto has_value(tl::expected const& exp) { 123 | return exp.has_value(); 124 | } 125 | 126 | /** 127 | * @brief Tests if any of the expected args passed in has an error. 128 | * 129 | * @param args tl::expected parameter pack 130 | * 131 | * @tparam E The error type 132 | * @tparam Args The value types for the tl::expected args 133 | * 134 | * @return The first error found or nothing 135 | */ 136 | template 137 | [[nodiscard]] constexpr auto maybe_error(tl::expected... args) { 138 | auto maybe = std::optional(); 139 | ( 140 | [&](auto& exp) { 141 | if (maybe.has_value()) return; 142 | if (has_error(exp)) maybe = exp.error(); 143 | }(args), 144 | ...); 145 | return maybe; 146 | } 147 | 148 | template 149 | constexpr inline bool is_optional_impl = false; 150 | template 151 | constexpr inline bool is_optional_impl> = true; 152 | template 153 | constexpr inline bool is_optional = is_optional_impl>>; 154 | 155 | } // namespace rsl 156 | 157 | /** 158 | * @brief Overload of the | operator as bind 159 | * 160 | * @param opt Input optional 161 | * @param fn Function 162 | * 163 | * @tparam T Input type 164 | * @tparam Fn Function 165 | * 166 | * @return Return type of f 167 | */ 168 | template >, 169 | typename = std::enable_if_t>::value_type>>> 171 | [[nodiscard]] constexpr auto operator|(T&& opt, Fn&& fn) { 172 | return rsl::mbind(std::forward(opt), std::forward(fn)); 173 | } 174 | 175 | /** 176 | * @brief Overload of the | operator as bind 177 | * 178 | * @param exp Input tl::expected value 179 | * @param fn Function to apply 180 | * 181 | * @tparam T Type for the input expected 182 | * @tparam E Error type 183 | * @tparam Fn Function 184 | * 185 | * @return Return type of fn 186 | */ 187 | template 188 | [[nodiscard]] constexpr auto operator|(tl::expected const& exp, Fn fn) { 189 | return rsl::mbind(exp, fn); 190 | } 191 | 192 | /** 193 | * @brief Overload of the | operator for unary functions 194 | * 195 | * @param val Input value 196 | * @param fn Function to apply on val 197 | * 198 | * @tparam T Type for the input 199 | * @tparam Fn Function 200 | * 201 | * @return Return the result of invoking the function on val 202 | */ 203 | template >> 204 | [[nodiscard]] constexpr auto operator|(T&& val, Fn&& fn) -> 205 | typename std::enable_if_t, std::invoke_result_t> { 206 | return std::invoke(std::forward(fn), std::forward(val)); 207 | } 208 | -------------------------------------------------------------------------------- /include/rsl/no_discard.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace rsl { 6 | 7 | /** @file */ 8 | 9 | /** 10 | * @brief Template for creating lambdas with the nodiscard attribute 11 | * 12 | * @tparam Fn Lambda 13 | */ 14 | template 15 | class NoDiscard { 16 | static_assert(std::is_invocable_v, "Fn must be invocable"); 17 | Fn fn_; 18 | 19 | public: 20 | NoDiscard(Fn const& fn) : fn_(fn) {} 21 | template 22 | [[nodiscard]] constexpr auto operator()(Ts&&... args) const 23 | noexcept(noexcept(fn_(std::forward(args)...))) { 24 | return fn_(std::forward(args)...); 25 | } 26 | }; 27 | 28 | } // namespace rsl 29 | -------------------------------------------------------------------------------- /include/rsl/overload.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace rsl { 4 | 5 | /** @file */ 6 | 7 | /** 8 | * @brief Class template for creating overloads sets to use with std::visit 9 | * 10 | * @tparam Ts Types 11 | */ 12 | template 13 | struct Overload : Ts... { 14 | using Ts::operator()...; 15 | }; 16 | 17 | template 18 | Overload(Ts...) -> Overload; 19 | 20 | } // namespace rsl 21 | -------------------------------------------------------------------------------- /include/rsl/parameter_validators.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | namespace rsl { 15 | 16 | /** @file */ 17 | 18 | /** 19 | * @cond DETAIL 20 | */ 21 | namespace detail { 22 | template 23 | [[nodiscard]] auto size_compare(rclcpp::Parameter const& parameter, size_t const size, 24 | std::string const& predicate_description, Fn const& predicate) 25 | -> tl::expected { 26 | static constexpr auto format_string = "Length of parameter '{}' is '{}' but must be {} '{}'"; 27 | switch (parameter.get_type()) { 28 | case rclcpp::ParameterType::PARAMETER_STRING: 29 | if (auto value = parameter.get_value(); !predicate(value.size(), size)) 30 | return tl::unexpected(fmt::format(format_string, parameter.get_name(), value.size(), 31 | predicate_description, size)); 32 | break; 33 | default: 34 | if (auto value = parameter.get_value>(); !predicate(value.size(), size)) 35 | return tl::unexpected(fmt::format(format_string, parameter.get_name(), value.size(), 36 | predicate_description, size)); 37 | } 38 | return {}; 39 | } 40 | 41 | template 42 | [[nodiscard]] auto compare(rclcpp::Parameter const& parameter, T const& value, 43 | std::string const& predicate_description, Fn const& predicate) 44 | -> tl::expected { 45 | if (auto const param_value = parameter.get_value(); !predicate(param_value, value)) 46 | return tl::unexpected(fmt::format("Parameter '{}' with the value '{}' must be {} '{}'", 47 | parameter.get_name(), param_value, predicate_description, 48 | value)); 49 | return {}; 50 | } 51 | } // namespace detail 52 | /** 53 | * @endcond 54 | */ 55 | 56 | /** 57 | * @brief Is every element of rclcpp::Parameter unique? 58 | * @pre rclcpp::Parameter must be an array type 59 | * @tparam T Interior type of array; e.g. for parameter type double_array, T = double 60 | * @return Help string if the parameter is invalid, otherwise void 61 | */ 62 | template 63 | [[nodiscard]] auto unique(rclcpp::Parameter const& parameter) -> tl::expected { 64 | if (is_unique(parameter.get_value>())) return {}; 65 | return tl::unexpected( 66 | fmt::format("Parameter '{}' must only contain unique values", parameter.get_name())); 67 | } 68 | 69 | /** 70 | * @brief Are the values in parameter a subset of the valid values? 71 | * @pre rclcpp::Parameter must be an array type 72 | * @tparam T Interior type of array; e.g. for parameter type double_array, T = double 73 | * @return Help string if the parameter is invalid, otherwise void 74 | */ 75 | template 76 | [[nodiscard]] auto subset_of(rclcpp::Parameter const& parameter, std::vector const& valid_values) 77 | -> tl::expected { 78 | auto const& values = parameter.get_value>(); 79 | for (auto const& value : values) 80 | if (!contains(valid_values, value)) 81 | return tl::unexpected( 82 | fmt::format("Entry '{}' in parameter '{}' is not in the set '{{{}}}'", value, 83 | parameter.get_name(), fmt::join(valid_values, ", "))); 84 | return {}; 85 | } 86 | 87 | /** 88 | * @brief Is the array size of parameter equal to passed in size? 89 | * @pre rclcpp::Parameter must be an array type 90 | * @tparam T Interior type of array; e.g. for parameter type double_array, T = double 91 | * @return Help string if the parameter is invalid, otherwise void 92 | */ 93 | template 94 | [[nodiscard]] auto fixed_size(rclcpp::Parameter const& parameter, size_t const size) { 95 | return detail::size_compare(parameter, size, "equal to", std::equal_to<>()); 96 | } 97 | 98 | /** 99 | * @brief Is the array size of parameter greater than passed in size? 100 | * @pre rclcpp::Parameter must be an array type 101 | * @tparam T Interior type of array; e.g. for parameter type double_array, T = double 102 | * @return Help string if the parameter is invalid, otherwise void 103 | */ 104 | template 105 | [[nodiscard]] auto size_gt(rclcpp::Parameter const& parameter, size_t const size) { 106 | return detail::size_compare(parameter, size, "greater than", std::greater<>()); 107 | } 108 | 109 | /** 110 | * @brief Is the array size of parameter less than passed in size? 111 | * @pre rclcpp::Parameter must be an array type 112 | * @tparam T Interior type of array; e.g. for parameter type double_array, T = double 113 | * @return Help string if the parameter is invalid, otherwise void 114 | */ 115 | template 116 | [[nodiscard]] auto size_lt(rclcpp::Parameter const& parameter, size_t const size) { 117 | return detail::size_compare(parameter, size, "less than", std::less<>()); 118 | } 119 | 120 | /** 121 | * @brief Is the size of the value passed in not zero? 122 | * @pre rclcpp::Parameter must be an array type or a string 123 | * @tparam T Interior type of array or std::string; e.g. for parameter type double_array, T 124 | * = double 125 | * @return Help string if the parameter is invalid, otherwise void 126 | */ 127 | template 128 | [[nodiscard]] auto not_empty(rclcpp::Parameter const& parameter) 129 | -> tl::expected { 130 | switch (parameter.get_type()) { 131 | case rclcpp::ParameterType::PARAMETER_STRING: 132 | if (auto param_value = parameter.get_value(); param_value.empty()) 133 | return tl::unexpected( 134 | fmt::format("Parameter '{}' cannot be empty", parameter.get_name())); 135 | break; 136 | default: 137 | if (auto param_value = parameter.get_value>(); param_value.empty()) 138 | return tl::unexpected( 139 | fmt::format("Parameter '{}' cannot be empty", parameter.get_name())); 140 | } 141 | return {}; 142 | } 143 | 144 | /** 145 | * @brief Are all elements of parameter within the bounds (inclusive)? 146 | * @pre rclcpp::Parameter must be an array type 147 | * @tparam T Interior type of array; e.g. for parameter type double_array, T = double 148 | * @return Help string if the parameter is invalid, otherwise void 149 | */ 150 | template 151 | [[nodiscard]] auto element_bounds(rclcpp::Parameter const& parameter, T const& lower, 152 | T const& upper) -> tl::expected { 153 | auto const& param_value = parameter.get_value>(); 154 | for (auto val : param_value) 155 | if (val < lower || val > upper) 156 | return tl::unexpected( 157 | fmt::format("Value '{}' in parameter '{}' must be within bounds '[{}, {}]'", val, 158 | parameter.get_name(), lower, upper)); 159 | return {}; 160 | } 161 | 162 | /** 163 | * @brief Are all elements of parameter greater than lower bound? 164 | * @pre rclcpp::Parameter must be an array type 165 | * @tparam T Interior type of array; e.g. for parameter type double_array, T = double 166 | * @return Help string if the parameter is invalid, otherwise void 167 | */ 168 | template 169 | [[nodiscard]] auto lower_element_bounds(rclcpp::Parameter const& parameter, T const& lower) 170 | -> tl::expected { 171 | auto const& param_value = parameter.get_value>(); 172 | for (auto val : param_value) 173 | if (val < lower) 174 | return tl::unexpected( 175 | fmt::format("Value '{}' in parameter '{}' must be above lower bound of '{}'", val, 176 | parameter.get_name(), lower)); 177 | return {}; 178 | } 179 | 180 | /** 181 | * @brief Are all elements of parameter less than some upper bound? 182 | * @pre rclcpp::Parameter must be an array type 183 | * @tparam T Interior type of array; e.g. for parameter type double_array, T = double 184 | * @return Help string if the parameter is invalid, otherwise void 185 | */ 186 | template 187 | [[nodiscard]] auto upper_element_bounds(rclcpp::Parameter const& parameter, T const& upper) 188 | -> tl::expected { 189 | auto const& param_value = parameter.get_value>(); 190 | for (auto val : param_value) 191 | if (val > upper) 192 | return tl::unexpected( 193 | fmt::format("Value '{}' in parameter '{}' must be below upper bound of '{}'", val, 194 | parameter.get_name(), upper)); 195 | return {}; 196 | } 197 | 198 | /** 199 | * @brief Is parameter within bounds (inclusive)? 200 | * @pre rclcpp::Parameter must be a non-array type 201 | * @tparam T Interior type of parameter; e.g. for parameter type int, T = int64_t 202 | * @return Help string if the parameter is invalid, otherwise void 203 | */ 204 | template 205 | [[nodiscard]] auto bounds(rclcpp::Parameter const& parameter, T const& lower, T const& upper) 206 | -> tl::expected { 207 | auto const& param_value = parameter.get_value(); 208 | if (param_value < lower || param_value > upper) 209 | return tl::unexpected( 210 | fmt::format("Parameter '{}' with the value '{}' must be within bounds '[{}, {}]'", 211 | parameter.get_name(), param_value, lower, upper)); 212 | return {}; 213 | } 214 | 215 | /** 216 | * @brief Is parameter within some lower bound (same as gt_eq)? 217 | * @pre rclcpp::Parameter must be a non-array type 218 | * @tparam T Interior type of parameter; e.g. for parameter type int, T = int64_t 219 | * @return Help string if the parameter is invalid, otherwise void 220 | */ 221 | template 222 | [[deprecated("Replace with rsl::gt_eq"), nodiscard]] auto lower_bounds( 223 | rclcpp::Parameter const& parameter, T const& value) { 224 | return detail::compare(parameter, value, "above lower bound of", std::greater_equal()); 225 | } 226 | 227 | /** 228 | * @brief Is parameter within some upper bound (same as lt_eq)? 229 | * @pre rclcpp::Parameter must be a non-array type 230 | * @tparam T Interior type of parameter; e.g. for parameter type int, T = int64_t 231 | * @return Help string if the parameter is invalid, otherwise void 232 | */ 233 | template 234 | [[deprecated("Replace with rsl::lt_eq"), nodiscard]] auto upper_bounds( 235 | rclcpp::Parameter const& parameter, T const& value) { 236 | return detail::compare(parameter, value, "below upper bound of", std::less_equal()); 237 | } 238 | 239 | /** 240 | * @brief Is parameter less than some value? 241 | * @pre rclcpp::Parameter must be a non-array type 242 | * @tparam T Interior type of parameter; e.g. for parameter type int, T = int64_t 243 | * @return Help string if the parameter is invalid, otherwise void 244 | */ 245 | template 246 | [[nodiscard]] auto lt(rclcpp::Parameter const& parameter, T const& value) { 247 | return detail::compare(parameter, value, "less than", std::less()); 248 | } 249 | 250 | /** 251 | * @brief Is parameter greater than some value? 252 | * @pre rclcpp::Parameter must be a non-array type 253 | * @tparam T Interior type of parameter; e.g. for parameter type int, T = int64_t 254 | * @return Help string if the parameter is invalid, otherwise void 255 | */ 256 | template 257 | [[nodiscard]] auto gt(rclcpp::Parameter const& parameter, T const& value) { 258 | return detail::compare(parameter, value, "greater than", std::greater()); 259 | } 260 | 261 | /** 262 | * @brief Is parameter less than or equal to some value? 263 | * @pre rclcpp::Parameter must be a non-array type 264 | * @tparam T Interior type of parameter; e.g. for parameter type int, T = int64_t 265 | * @return Help string if the parameter is invalid, otherwise void 266 | */ 267 | template 268 | [[nodiscard]] auto lt_eq(rclcpp::Parameter const& parameter, T const& value) { 269 | return detail::compare(parameter, value, "less than or equal to", std::less_equal()); 270 | } 271 | 272 | /** 273 | * @brief Is parameter greater than or equal to some value? 274 | * @pre rclcpp::Parameter must be a non-array type 275 | * @tparam T Interior type of parameter; e.g. for parameter type int, T = int64_t 276 | * @return Help string if the parameter is invalid, otherwise void 277 | */ 278 | template 279 | [[nodiscard]] auto gt_eq(rclcpp::Parameter const& parameter, T const& value) { 280 | return detail::compare(parameter, value, "greater than or equal to", std::greater_equal()); 281 | } 282 | 283 | /** 284 | * @brief Is the parameter value one of a set of values? 285 | * @pre rclcpp::Parameter must be a non-array type 286 | * @tparam T Interior type of parameter; e.g. for parameter type int, T = int64_t 287 | * @return Help string if the parameter is invalid, otherwise void 288 | */ 289 | template 290 | [[nodiscard]] auto one_of(rclcpp::Parameter const& parameter, std::vector const& collection) 291 | -> tl::expected { 292 | auto const& param_value = parameter.get_value(); 293 | if (contains(collection, param_value)) return {}; 294 | return tl::unexpected(fmt::format( 295 | "Parameter '{}' with the value '{}' is not in the set '{{{}}}'", parameter.get_name(), 296 | param_value, fmt::format("{}", fmt::join(collection, ", ")))); 297 | } 298 | 299 | /** 300 | * @brief Convert the result of a validator function into a SetParametersResult msg 301 | */ 302 | [[nodiscard]] RSL_EXPORT auto to_parameter_result_msg(tl::expected const& result) 303 | -> rcl_interfaces::msg::SetParametersResult; 304 | 305 | } // namespace rsl 306 | -------------------------------------------------------------------------------- /include/rsl/queue.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace rsl { 10 | 11 | /** @file */ 12 | 13 | /** 14 | * @brief Thread-safe queue. Particularly useful when multiple threads need to write to and/or read 15 | * from a queue. 16 | */ 17 | template 18 | class Queue { 19 | std::queue queue_; 20 | std::condition_variable cv_; 21 | mutable std::mutex mutex_; 22 | 23 | public: 24 | /** 25 | * @brief Get the size of the queue 26 | * @return Queue size 27 | */ 28 | [[nodiscard]] auto size() const noexcept { 29 | auto const lock = std::lock_guard(mutex_); 30 | return queue_.size(); 31 | } 32 | 33 | /** 34 | * @brief Check if the queue is empty 35 | * @return True if the queue is empty, otherwise false 36 | */ 37 | [[nodiscard]] auto empty() const noexcept { 38 | auto const lock = std::lock_guard(mutex_); 39 | return queue_.empty(); 40 | } 41 | 42 | /** 43 | * @brief Push data into the queue 44 | * @param value Data to push into the queue 45 | */ 46 | void push(T value) noexcept { 47 | auto const lock = std::lock_guard(mutex_); 48 | queue_.push(std::move(value)); 49 | cv_.notify_one(); 50 | } 51 | 52 | /** 53 | * @brief Clear the queue 54 | */ 55 | void clear() noexcept { 56 | auto const lock = std::lock_guard(mutex_); 57 | 58 | // Swap queue with an empty queue of the same type to ensure queue_ is left in a 59 | // default-constructed state 60 | decltype(queue_)().swap(queue_); 61 | } 62 | 63 | /** 64 | * @brief Wait for given duration then pop from the queue and return the element 65 | * @param wait_time Maximum time to wait for queue to be non-empty 66 | * @return Data popped from the queue or error 67 | */ 68 | [[nodiscard]] auto pop(std::chrono::nanoseconds wait_time = {}) -> std::optional { 69 | auto lock = std::unique_lock(mutex_); 70 | 71 | // If queue is empty after wait_time, return nothing 72 | if (!cv_.wait_for(lock, wait_time, [this] { return !queue_.empty(); })) return std::nullopt; 73 | 74 | auto value = queue_.front(); 75 | queue_.pop(); 76 | return value; 77 | } 78 | }; 79 | } // namespace rsl 80 | -------------------------------------------------------------------------------- /include/rsl/random.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace rsl { 11 | 12 | /** @file */ 13 | 14 | /** 15 | * @brief Get a random number generator 16 | * 17 | * The first time this function is called it creates a thread_local random number generator. If a 18 | * seed sequence is provided on that first call it is used to create the generator, otherwise the 19 | * random device is used to seed the generator. After the first call to this function, if a seed 20 | * sequence is provided this function throws. The returned value is a reference to a thread_local 21 | * static generator. 22 | * 23 | * @param seed_sequence Seed sequence for random number generator 24 | * 25 | * @return Seeded random number generator 26 | */ 27 | RSL_EXPORT auto rng(std::seed_seq seed_sequence = {}) -> std::mt19937&; 28 | 29 | /** 30 | * @brief Get a uniform real number in a given range 31 | * 32 | * @param lower Lower bound, inclusive 33 | * @param upper Upper bound, exclusive 34 | * 35 | * @tparam RealType Floating point type 36 | * 37 | * @return Uniform real in range [lower, upper) 38 | */ 39 | template 40 | [[nodiscard]] auto uniform_real(RealType lower, RealType upper) { 41 | static_assert(std::is_floating_point_v, "RealType must be a floating point type"); 42 | assert(lower < upper && "rsl::uniform_real: Lower bound be less than upper bound"); 43 | return std::uniform_real_distribution(lower, upper)(rng()); 44 | } 45 | 46 | /** 47 | * @brief Get a uniform integer number in a given range 48 | * 49 | * @param lower Lower bound, inclusive 50 | * @param upper Upper bound, inclusive 51 | * 52 | * @tparam IntType Integral type 53 | * 54 | * @return Uniform integer in range [lower, upper] 55 | */ 56 | template 57 | [[nodiscard]] auto uniform_int(IntType lower, IntType upper) { 58 | static_assert(std::is_integral_v, "IntType must be an integral type"); 59 | assert(lower <= upper && 60 | "rsl::uniform_int: Lower bound must be less than or equal to upper bound"); 61 | return std::uniform_int_distribution(lower, upper)(rng()); 62 | } 63 | 64 | /** 65 | * @brief Generate a random unit quaternion of doubles 66 | * @return Random unit quaternion 67 | */ 68 | [[nodiscard]] RSL_EXPORT auto random_unit_quaternion() -> Eigen::Quaterniond; 69 | 70 | } // namespace rsl 71 | -------------------------------------------------------------------------------- /include/rsl/static_string.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace rsl { 9 | 10 | /** @file */ 11 | 12 | /** 13 | * @brief Fixed capacity string with an implicit conversion to std::string_view. Capacity is 14 | * specified as a template parameter. At runtime one may use up to the specified capacity. 15 | */ 16 | template 17 | class StaticString { 18 | std::array data_{}; 19 | size_t size_{}; 20 | 21 | public: 22 | /** 23 | * @brief Construct an empty string 24 | */ 25 | StaticString() = default; 26 | 27 | /** 28 | * @brief Construct from a std::string 29 | */ 30 | StaticString(std::string const& string) : size_(std::min(string.size(), capacity)) { 31 | assert(string.size() <= capacity && 32 | "rsl::StaticString::StaticString: Input exceeds capacity"); 33 | std::copy(string.cbegin(), string.cbegin() + std::string::difference_type(size_), 34 | data_.begin()); 35 | } 36 | /** 37 | * @brief Get a const begin iterator 38 | */ 39 | [[nodiscard]] auto begin() const { return data_.cbegin(); } 40 | 41 | /** 42 | * @brief Get a const end iterator 43 | */ 44 | [[nodiscard]] auto end() const { return data_.cbegin() + size_; } 45 | 46 | /** 47 | * @brief Implicit conversion to std::string_view 48 | */ 49 | operator std::string_view() const { return std::string_view(data_.data(), size_); } 50 | }; 51 | 52 | /** 53 | * @brief Explicit conversion to std::string 54 | */ 55 | template 56 | [[nodiscard]] auto to_string(StaticString const& static_string) { 57 | return std::string(static_string); 58 | } 59 | 60 | } // namespace rsl 61 | -------------------------------------------------------------------------------- /include/rsl/static_vector.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace rsl { 10 | 11 | /** @file */ 12 | 13 | /** 14 | * @brief Fixed capacity vector with an implicit conversion to tcb::span. Capacity is specified as 15 | * a template parameter. At runtime one may use up to the specified capacity. 16 | */ 17 | template 18 | class StaticVector { 19 | std::array data_{}; 20 | size_t size_{}; 21 | 22 | public: 23 | /** 24 | * @brief Construct an empty vector 25 | */ 26 | StaticVector() = default; 27 | 28 | /** 29 | * @brief Construct from another container 30 | */ 31 | template 32 | StaticVector(Collection const& collection) : size_(std::min(collection.size(), capacity)) { 33 | assert(collection.size() <= capacity && 34 | "rsl::StaticVector::StaticVector: Input exceeds capacity"); 35 | std::copy(collection.cbegin(), 36 | collection.cbegin() + typename Collection::difference_type(size_), data_.begin()); 37 | } 38 | 39 | /** 40 | * @brief Construct from a std::initializer_list 41 | */ 42 | StaticVector(std::initializer_list const& collection) 43 | : StaticVector(std::vector(collection)) {} 44 | 45 | /** 46 | * @brief Get a mutable begin iterator 47 | */ 48 | [[nodiscard]] auto begin() { return data_.begin(); } 49 | 50 | /** 51 | * @brief Get a const begin iterator 52 | */ 53 | [[nodiscard]] auto begin() const { return data_.cbegin(); } 54 | 55 | /** 56 | * @brief Get a mutable end iterator 57 | */ 58 | [[nodiscard]] auto end() { return data_.begin() + size_; } 59 | 60 | /** 61 | * @brief Get a const end iterator 62 | */ 63 | [[nodiscard]] auto end() const { return data_.cbegin() + size_; } 64 | 65 | /** 66 | * @brief Implicit conversion to tcb::span 67 | */ 68 | operator tcb::span() { return tcb::span(data_.data(), size_); } 69 | 70 | /** 71 | * @brief Implicit conversion to tcb::span 72 | */ 73 | operator tcb::span() const { return tcb::span(data_.data(), size_); } 74 | }; 75 | 76 | /** 77 | * @brief Explicit conversion to std::vector 78 | */ 79 | template 80 | [[nodiscard]] auto to_vector(StaticVector const& static_vector) { 81 | return std::vector(static_vector.begin(), static_vector.end()); 82 | } 83 | 84 | } // namespace rsl 85 | -------------------------------------------------------------------------------- /include/rsl/strong_type.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace rsl { 6 | 7 | /** @file */ 8 | 9 | /** 10 | * @brief Class template for creating strong type aliases 11 | * 12 | * @tparam T value type 13 | * @tparam Tag Tag type to disambiguate separate type aliases 14 | */ 15 | template 16 | class StrongType { 17 | T value_; 18 | 19 | public: 20 | /** 21 | * @brief Construct from any type 22 | */ 23 | constexpr explicit StrongType(T value) : value_(std::move(value)) {} 24 | 25 | /** 26 | * @brief Get non-const reference to underlying value 27 | */ 28 | [[nodiscard]] constexpr T& get() { return value_; } 29 | 30 | /** 31 | * @brief Get const reference to underlying value 32 | */ 33 | [[nodiscard]] constexpr const T& get() const { return value_; } 34 | 35 | /** 36 | * @brief Explicit conversion to underlying type 37 | */ 38 | [[nodiscard]] constexpr explicit operator T() const { return value_; } 39 | }; 40 | 41 | } // namespace rsl 42 | -------------------------------------------------------------------------------- /include/rsl/try.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** @file */ 6 | 7 | /** 8 | * @brief Unwrap an expected into a stack variable or return the unexpected value 9 | * 10 | * This macro requires the use of non-standard C++. Compiler extensions must be enabled and 11 | * -Wpedantic cannot be used. With GCC and Clang, you can disable -Wpedantic for an entire 12 | * translation unit like this: 13 | * 14 | * ```cpp 15 | * #pragma GCC diagnostic ignored "-Wpedantic" 16 | * ``` 17 | 18 | * You can disable -Wpedantic for a specific segment of code like so: 19 | * 20 | * ```cpp 21 | * #pragma GCC diagnostic push 22 | * #pragma GCC diagnostic ignored "-Wpedantic" 23 | * //... 24 | * #pragma GCC diagnostic pop 25 | * ``` 26 | * 27 | * For more information on this particular compiler extension go to 28 | * https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html 29 | */ 30 | #define TRY(expected) \ 31 | ({ \ 32 | auto const& _expected = (expected); /* Uglify name to prevent shadowing */ \ 33 | if (!_expected.has_value()) return tl::unexpected(_expected.error()); \ 34 | _expected.value(); \ 35 | }) 36 | -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | rsl 5 | 1.2.0 6 | ROS Support Library 7 | Tyler Weaver 8 | Chris Thrasher 9 | Tyler Weaver 10 | Chris Thrasher 11 | BSD-3-Clause 12 | 13 | doxygen 14 | 15 | eigen 16 | fmt 17 | rclcpp 18 | tcb_span 19 | tl_expected 20 | 21 | ament_cmake_ros 22 | clang-tidy 23 | git 24 | range-v3 25 | 26 | -------------------------------------------------------------------------------- /src/parameter_validators.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace rsl { 4 | 5 | auto to_parameter_result_msg(tl::expected const& result) 6 | -> rcl_interfaces::msg::SetParametersResult { 7 | auto msg = rcl_interfaces::msg::SetParametersResult(); 8 | msg.successful = result.has_value(); 9 | msg.reason = result.has_value() ? "" : result.error(); 10 | return msg; 11 | } 12 | 13 | } // namespace rsl 14 | -------------------------------------------------------------------------------- /src/random.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace rsl { 10 | 11 | auto rng(std::seed_seq seed_sequence) -> std::mt19937& { 12 | thread_local auto generator = std::optional(); 13 | 14 | // Prevent reseeding the generator 15 | if (generator.has_value() && seed_sequence.size() > 0) 16 | throw std::runtime_error("rng cannot be re-seeded on this thread"); 17 | 18 | // Return existing generator 19 | if (generator.has_value() && seed_sequence.size() == 0) return generator.value(); 20 | 21 | // Seed with specified sequence 22 | if (seed_sequence.size() > 0) return generator.emplace(seed_sequence); 23 | 24 | // Seed with randomized sequence 25 | auto seed_data = std::array(); 26 | auto random_device = std::random_device(); 27 | std::generate_n(seed_data.data(), seed_data.size(), std::ref(random_device)); 28 | auto sequence = std::seed_seq(seed_data.begin(), seed_data.end()); 29 | return generator.emplace(sequence); 30 | } 31 | 32 | auto random_unit_quaternion() -> Eigen::Quaterniond { 33 | static constexpr auto pi = 3.1415926535897932385; 34 | 35 | // From "Uniform Random Rotations", Ken Shoemake, Graphics Gems III, pg. 124-132 36 | auto const x0 = uniform_real(0., 1.); 37 | auto const r1 = std::sqrt(1 - x0); 38 | auto const r2 = std::sqrt(x0); 39 | auto const t1 = uniform_real(0., 2 * pi); 40 | auto const t2 = uniform_real(0., 2 * pi); 41 | auto const x = r1 * std::sin(t1); 42 | auto const y = r1 * std::cos(t1); 43 | auto const z = r2 * std::sin(t2); 44 | auto const w = r2 * std::cos(t2); 45 | return Eigen::Quaterniond(w, x, y, z).normalized(); 46 | } 47 | 48 | } // namespace rsl 49 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) 2 | 3 | find_package(Catch2 3.6.0 REQUIRED) 4 | find_package(range-v3 REQUIRED) 5 | 6 | # Test library 7 | add_executable(test-rsl 8 | algorithm.cpp 9 | monad.cpp 10 | no_discard.cpp 11 | overload.cpp 12 | parameter_validators.cpp 13 | queue.cpp 14 | random.cpp 15 | static_string.cpp 16 | static_vector.cpp 17 | strong_type.cpp) 18 | if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)") 19 | target_sources(test-rsl PRIVATE try.cpp) # Requires GCC extensions 20 | endif() 21 | target_link_libraries(test-rsl PRIVATE 22 | rsl::rsl 23 | Catch2::Catch2WithMain 24 | range-v3::range-v3 25 | ) 26 | catch_discover_tests(test-rsl) 27 | 28 | # Test install interface 29 | add_test(NAME "Install RSL" COMMAND 30 | ${CMAKE_COMMAND} 31 | --install ${PROJECT_BINARY_DIR} 32 | --prefix ${CMAKE_CURRENT_BINARY_DIR}/install/RSL 33 | --config $) 34 | set_tests_properties("Install RSL" PROPERTIES FIXTURES_SETUP install-rsl) 35 | 36 | add_test(NAME "Configure installation test" COMMAND 37 | ${CMAKE_COMMAND} 38 | -S ${CMAKE_CURRENT_SOURCE_DIR}/install 39 | -B ${CMAKE_CURRENT_BINARY_DIR}/install/build 40 | -G ${CMAKE_GENERATOR} 41 | -DCMAKE_BUILD_TYPE=$ 42 | -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} 43 | -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} 44 | -Drsl_ROOT=${CMAKE_CURRENT_BINARY_DIR}/install/RSL) 45 | set_tests_properties("Configure installation test" PROPERTIES FIXTURES_SETUP configure-test-project FIXTURES_REQUIRED install-rsl) 46 | 47 | add_test(NAME "Build installation test" COMMAND ${CMAKE_COMMAND} --build ${CMAKE_CURRENT_BINARY_DIR}/install/build --config $) 48 | set_tests_properties("Build installation test" PROPERTIES FIXTURES_REQUIRED configure-test-project) 49 | 50 | add_test(NAME "Delete installation" COMMAND ${CMAKE_COMMAND} -E rm -r ${CMAKE_CURRENT_BINARY_DIR}/install/RSL ${CMAKE_CURRENT_BINARY_DIR}/install/build) 51 | set_tests_properties("Delete installation" PROPERTIES FIXTURES_CLEANUP "install-rsl;configure-test-project") 52 | 53 | # Build install test project 54 | add_subdirectory(install) 55 | -------------------------------------------------------------------------------- /tests/FindCatch2.cmake: -------------------------------------------------------------------------------- 1 | include(FetchContent) 2 | 3 | FetchContent_Declare(Catch2 4 | GIT_REPOSITORY https://github.com/catchorg/Catch2.git 5 | GIT_TAG v${Catch2_FIND_VERSION}) 6 | FetchContent_MakeAvailable(Catch2) 7 | set_target_properties(Catch2 PROPERTIES COMPILE_OPTIONS "" EXPORT_COMPILE_COMMANDS OFF) 8 | set_target_properties(Catch2WithMain PROPERTIES EXPORT_COMPILE_COMMANDS OFF) 9 | get_target_property(CATCH2_INCLUDE_DIRS Catch2 INTERFACE_INCLUDE_DIRECTORIES) 10 | target_include_directories(Catch2 SYSTEM INTERFACE ${CATCH2_INCLUDE_DIRS}) 11 | include(Catch) 12 | -------------------------------------------------------------------------------- /tests/algorithm.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | TEST_CASE("rsl::contains") { 10 | SECTION("No items") { 11 | CHECK_FALSE(rsl::contains(std::array{}, 0)); 12 | CHECK_FALSE(rsl::contains(std::vector{}, 0)); 13 | } 14 | 15 | SECTION("One item") { 16 | auto const values = std::vector{-1}; 17 | CHECK(rsl::contains(values, -1)); 18 | CHECK_FALSE(rsl::contains(values, 0)); 19 | } 20 | 21 | SECTION("Two items that are the same") { 22 | auto const values = std::array{-1., -1.}; 23 | CHECK(rsl::contains(values, -1.)); 24 | CHECK_FALSE(rsl::contains(values, 0.)); 25 | } 26 | 27 | SECTION("Two items that are different") { 28 | auto const values = std::set{-1, 1}; 29 | CHECK(rsl::contains(values, -1)); 30 | CHECK(rsl::contains(values, 1)); 31 | CHECK_FALSE(rsl::contains(values, 0)); 32 | } 33 | } 34 | 35 | TEST_CASE("rsl::is_unique") { 36 | SECTION("No items") { 37 | CHECK(rsl::is_unique(std::array{})); 38 | CHECK(rsl::is_unique(std::vector{})); 39 | } 40 | 41 | SECTION("One item") { 42 | CHECK(rsl::is_unique(std::array{42, 117})); 43 | CHECK(rsl::is_unique(std::vector{-1})); 44 | } 45 | 46 | SECTION("Two items that are the same") { 47 | CHECK_FALSE(rsl::is_unique(std::array{99, 99})); 48 | CHECK_FALSE(rsl::is_unique(std::vector{-1, -1})); 49 | } 50 | 51 | SECTION("Two items that are different") { 52 | CHECK(rsl::is_unique(std::array{-1, 1})); 53 | CHECK(rsl::is_unique(std::vector{-1, 1})); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/install/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | project(rsl-install-test CXX) 3 | 4 | add_executable(test-rsl-install install.cpp) 5 | 6 | if(PROJECT_IS_TOP_LEVEL) 7 | # Test consumption via find_package interface and ament_target_dependencies 8 | find_package(ament_cmake_ros REQUIRED) 9 | find_package(rsl 1.1.0 EXACT REQUIRED) 10 | 11 | ament_target_dependencies(test-rsl-install rsl) 12 | else() 13 | # Test consumption via in-tree build 14 | target_link_libraries(test-rsl-install PRIVATE rsl::rsl) 15 | endif() 16 | -------------------------------------------------------------------------------- /tests/install/install.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | auto& rng = rsl::rng(); 5 | return std::uniform_int_distribution(0, 1)(rng); 6 | } 7 | -------------------------------------------------------------------------------- /tests/monad.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | using namespace std::string_literals; 11 | 12 | namespace { 13 | constexpr auto maybe_non_zero(int in) { return (in != 0) ? std::optional(in) : std::nullopt; } 14 | 15 | auto maybe_lt_3_round(double in) { 16 | return (in < 3) ? std::optional(std::round(in)) : std::nullopt; 17 | } 18 | 19 | auto unsafe_divide_4_by(double val) { 20 | if (val == 0) throw std::runtime_error("divide by zero"); 21 | return 4.0 / val; 22 | } 23 | 24 | template 25 | using Result = tl::expected; 26 | 27 | Result divide(double x, double y) { 28 | if (y == 0) return tl::unexpected("divide by 0"s); 29 | return x / y; 30 | } 31 | 32 | Result multiply(double x, double y) { return x * y; } 33 | 34 | Result divide_3(double x) { return divide(3, x); } 35 | 36 | Result multiply_3(double x) { return multiply(3, x); } 37 | } // namespace 38 | 39 | TEST_CASE("rsl::mbind") { 40 | SECTION("Optional value") { 41 | // GIVEN optional value 4 42 | constexpr auto opt = std::optional(4); 43 | 44 | // WHEN we rsl::mbind it with the function maybe_non_zero 45 | // THEN we expect it true 46 | STATIC_REQUIRE(rsl::mbind(opt, maybe_non_zero)); 47 | } 48 | 49 | SECTION("Overloaded optional value") { 50 | // GIVEN optional value -4.0 51 | constexpr auto opt = std::optional(-4.0); 52 | 53 | // WHEN we rsl::mbind it with two functions chained with operator| overload 54 | // THEN we expect it true 55 | CHECK((opt | maybe_lt_3_round | maybe_non_zero)); 56 | } 57 | 58 | SECTION("Optional no value") { 59 | // GIVEN optional non value 60 | constexpr auto opt = std::optional(); 61 | 62 | // WHEN we rsl::mbind it with the function maybe_non_zero 63 | // THEN we expect it false 64 | CHECK_FALSE(rsl::mbind(opt, maybe_non_zero)); 65 | } 66 | 67 | SECTION("Optional no value output") { 68 | // GIVEN optional value with 0.0 69 | constexpr auto opt = std::optional(0); 70 | 71 | // WHEN we rsl::mbind it with the function maybe_non_zero 72 | // THEN we expect it false 73 | CHECK_FALSE(rsl::mbind(opt, maybe_non_zero)); 74 | } 75 | 76 | SECTION("Try") { 77 | // GIVEN input value of 0.0 78 | constexpr auto input = 0.0; 79 | 80 | // WHEN we pass unsafe_divide_4_by to rsl::mtry with out input 81 | // THEN we expect it to not throw 82 | CHECK_NOTHROW(rsl::mtry(std::bind(&unsafe_divide_4_by, input))); 83 | } 84 | 85 | SECTION("Try ok") { 86 | // GIVEN input value of 4.3 87 | constexpr auto input = 4.3; 88 | 89 | // WHEN we pass unsafe_divide_4_by to rsl::mtry with out input 90 | // THEN we expect it to not throw 91 | CHECK_NOTHROW(rsl::mtry(std::bind(&unsafe_divide_4_by, input))); 92 | } 93 | 94 | SECTION("Compose two") { 95 | // GIVEN the functions maybe_non_zero and maybe_lt_3_round and an input opt 96 | constexpr auto opt = std::optional(-4.0); 97 | 98 | // WHEN we compose them together and then bind them with an input 99 | auto const compose_fn = rsl::mcompose(maybe_lt_3_round, maybe_non_zero); 100 | auto const compose_result = opt | compose_fn; 101 | 102 | // THEN we expect the result to be the same as if we chained the calls together 103 | auto const chain_result = opt | maybe_lt_3_round | maybe_non_zero; 104 | 105 | CHECK(compose_result == chain_result); 106 | } 107 | 108 | SECTION("Compose three") { 109 | // GIVEN the functions maybe_non_zero and maybe_lt_3_round and an input opt 110 | auto const opt = std::optional(-4.0); 111 | 112 | // WHEN we compose them together multiple times and then bind them with an input 113 | auto const compose_result = 114 | opt | rsl::mcompose(maybe_lt_3_round, maybe_non_zero, maybe_non_zero); 115 | 116 | // THEN we expect the result to be the same as if we chained the calls together 117 | auto const chain_result = opt | maybe_lt_3_round | maybe_non_zero | maybe_non_zero; 118 | 119 | CHECK(compose_result == chain_result); 120 | } 121 | 122 | SECTION("Pass unexpected value through bind") { 123 | Result const input = tl::unexpected("foo"s); 124 | auto const result = rsl::mbind(input, multiply_3); 125 | REQUIRE(rsl::has_error(result)); 126 | CHECK(result.error() == "foo"); 127 | } 128 | 129 | SECTION("Divide by 0 produces error with valid input") { 130 | auto const input = Result{0.0}; 131 | auto const result = rsl::mbind(input, divide_3); 132 | REQUIRE(rsl::has_error(result)); 133 | CHECK(result.error() == "divide by 0"); 134 | } 135 | 136 | SECTION("operator| divide by 0") { 137 | auto const input = Result{0.0}; 138 | auto const result = input | divide_3; 139 | REQUIRE(rsl::has_error(result)); 140 | CHECK(result.error() == "divide by 0"); 141 | } 142 | 143 | SECTION("operator| valid result") { 144 | CHECK((Result{5.0} | divide_3).value() == Catch::Approx(3 / 5.)); 145 | } 146 | 147 | SECTION("operator| two operations") { 148 | CHECK((Result{5.0} | divide_3 | multiply_3).value() == Catch::Approx(3 * (3 / 5.))); 149 | } 150 | 151 | SECTION("operator| different types") { 152 | auto const result = Result{5.0} | [](double in) -> Result { 153 | auto const out = static_cast(in); 154 | auto const check = static_cast(out); 155 | if (check != in) { 156 | return tl::unexpected("no worky casty"s); 157 | } 158 | return out; 159 | }; 160 | CHECK(result.value() == 5); 161 | } 162 | } 163 | 164 | TEST_CASE("rsl::has_error") { 165 | SECTION("Error") { 166 | // GIVEN expected type containing error 167 | auto const exp = tl::expected(tl::unexpected(0.1)); 168 | 169 | // WHEN calling rsl::has_value and tl::expected::has_value 170 | // THEN we expect rsl::has_value is true, and tl::expected::has_value is false 171 | CHECK(rsl::has_error(exp)); 172 | CHECK_FALSE(exp.has_value()); 173 | } 174 | 175 | SECTION("Value") { 176 | // GIVEN expected type containing value 177 | auto const exp = tl::expected(1); 178 | 179 | // WHEN calling rsl::has_value and tl::expected::has_value 180 | // THEN we expect rsl::has_value is false, and tl::expected::has_value is true 181 | CHECK_FALSE(rsl::has_error(exp)); 182 | CHECK(exp.has_value()); 183 | } 184 | } 185 | 186 | TEST_CASE("rsl::has_value") { 187 | SECTION("Error") { 188 | // GIVEN expected type containing error 189 | auto const exp = tl::expected(tl::unexpected(0.1)); 190 | 191 | // WHEN calling rsl::has_value and tl::expected::has_value 192 | // THEN we expect has_error is false, and has_value is false 193 | CHECK_FALSE(rsl::has_value(exp)); 194 | CHECK_FALSE(exp.has_value()); 195 | } 196 | 197 | SECTION("Value") { 198 | // GIVEN expected type containing value 199 | auto const exp = tl::expected(1); 200 | 201 | // WHEN calling rsl::has_value and tl::expected::has_value 202 | // THEN we expect has_error is true, and has_value is true 203 | CHECK(rsl::has_value(exp)); 204 | CHECK(exp.has_value()); 205 | } 206 | } 207 | 208 | TEST_CASE("operator|") { 209 | auto const i = [](auto x) { return x; }; 210 | auto const k = [](auto x) { return [=](auto) { return x; }; }; 211 | auto const mul = [](auto x, auto y) { return x * y; }; 212 | auto const bind1 = [](auto fn, auto x) { return [=](auto y) { return fn(x, y); }; }; 213 | auto const three = [](auto) { return 3; }; 214 | auto const wrap = [](auto x) { return Result{x}; }; 215 | 216 | CHECK((int{5} | i) == 5); 217 | CHECK((int{5} | i | k(3)) == 3); 218 | CHECK(((int{7} | i | k)(3)) == 7); 219 | CHECK((int{4} | bind1(mul, 3)) == 12); 220 | CHECK((double{5} | three) == 3); 221 | CHECK((double{5} | wrap).value() == Catch::Approx(5)); 222 | CHECK((double{5} | wrap | multiply_3).value() == Catch::Approx(5 * 3)); 223 | 224 | SECTION("range-v3 operator| still works") { 225 | auto v = {20, 10, 15}; 226 | auto r_inv = v | ranges::views::transform([](int x) { return 1.0 / x; }); 227 | auto val = 1.0 / ranges::accumulate(r_inv, 0.0); 228 | CHECK(val == Catch::Approx(4.61538)); 229 | } 230 | } 231 | 232 | TEST_CASE("rsl::maybe_error") { 233 | SECTION("No error") { 234 | CHECK(rsl::maybe_error(tl::expected(42)) == std::nullopt); 235 | CHECK(rsl::maybe_error(tl::expected(1), tl::expected(2), 236 | tl::expected(3), tl::expected(4), 237 | tl::expected(5)) == std::nullopt); 238 | } 239 | 240 | SECTION("Errors") { 241 | CHECK(rsl::maybe_error(tl::unexpected("oops")) == 242 | std::optional("oops")); 243 | CHECK(rsl::maybe_error(tl::expected(tl::unexpect, "oops1"), 244 | tl::expected(tl::unexpect, "oops2"), 245 | tl::expected(tl::unexpect, "oops3")) == 246 | std::optional("oops1")); 247 | CHECK(rsl::maybe_error(tl::expected(tl::unexpect, "oops1"), 248 | tl::expected(1337), 249 | tl::expected(tl::unexpect, "oops2"), 250 | tl::expected(tl::unexpect, "oops3")) == 251 | std::optional("oops1")); 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /tests/no_discard.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | TEST_CASE("rsl::NoDiscard") { 6 | auto const increment = rsl::NoDiscard([](int i) { return ++i; }); 7 | (void)increment(10); // Removing (void) should break the build 8 | CHECK(increment(10) == 11); 9 | 10 | auto const multiply = rsl::NoDiscard([](double x, double y) { return x * y; }); 11 | (void)multiply(2, 3); // Removing (void) should break the build 12 | CHECK(multiply(2, 3) == 6); 13 | } 14 | -------------------------------------------------------------------------------- /tests/overload.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | TEST_CASE("rsl::Overload") { 8 | enum class Type : std::uint8_t { INT, FLOAT, STRING }; 9 | 10 | auto const overload = 11 | rsl::Overload{[](int) { return Type::INT; }, [](float) { return Type::FLOAT; }, 12 | [](std::string const&) { return Type::STRING; }}; 13 | 14 | auto variant = std::variant(); 15 | CHECK(std::visit(overload, variant = 12) == Type::INT); 16 | CHECK(std::visit(overload, variant = 42.f) == Type::FLOAT); 17 | CHECK(std::visit(overload, variant = "PickNik") == Type::STRING); 18 | } 19 | -------------------------------------------------------------------------------- /tests/parameter_validators.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | using namespace std::literals; 6 | using rclcpp::Parameter; 7 | 8 | TEST_CASE("rsl::unique") { 9 | CHECK(rsl::unique(Parameter("", std::vector{"", "1", "2"}))); 10 | CHECK(rsl::unique(Parameter("", std::vector{}))); 11 | CHECK(!rsl::unique(Parameter("", std::vector{"foo", "foo"}))); 12 | 13 | CHECK(rsl::unique(Parameter("", std::vector{1.0, 2.2, 1.1}))); 14 | CHECK(rsl::unique(Parameter("", std::vector{}))); 15 | CHECK(!rsl::unique(Parameter("", std::vector{1.1, 1.1}))); 16 | 17 | CHECK(rsl::unique(Parameter("", std::vector{1, 2, 3}))); 18 | CHECK(rsl::unique(Parameter("", std::vector{}))); 19 | CHECK(!rsl::unique(Parameter("", std::vector{-1, -1}))); 20 | 21 | CHECK(rsl::unique(Parameter("", std::vector{true, false}))); 22 | CHECK(rsl::unique(Parameter("", std::vector{}))); 23 | CHECK(!rsl::unique(Parameter("", std::vector{false, false}))); 24 | 25 | auto const result = 26 | rsl::unique(Parameter("test", std::vector{"foo", "foo"})); 27 | REQUIRE(!result); 28 | CHECK(result.error() == "Parameter 'test' must only contain unique values"); 29 | } 30 | 31 | TEST_CASE("rsl::subset_of") { 32 | CHECK(rsl::subset_of(Parameter("", std::vector{"", "1", "2"}), 33 | std::vector{"", "1", "2", "three"})); 34 | CHECK(rsl::subset_of(Parameter("", std::vector{}), 35 | std::vector{"", "1", "2", "three"})); 36 | CHECK(!rsl::subset_of(Parameter("", std::vector{"foo", "foo"}), 37 | std::vector{"", "1", "2", "three"})); 38 | 39 | CHECK(rsl::subset_of(Parameter("", std::vector{1.0, 2.2, 1.1}), 40 | std::vector{1.0, 2.2, 1.1})); 41 | CHECK( 42 | rsl::subset_of(Parameter("", std::vector{}), std::vector{10, 22})); 43 | CHECK(!rsl::subset_of(Parameter("", std::vector{1.1, 1.1}), 44 | std::vector{1.0, 2.2})); 45 | 46 | CHECK(rsl::subset_of(Parameter("", std::vector{1, 2, 3}), 47 | std::vector{1, 2, 3})); 48 | CHECK(rsl::subset_of(Parameter("", std::vector{}), 49 | std::vector{1, 2, 3})); 50 | CHECK(!rsl::subset_of(Parameter("", std::vector{-1, -1}), 51 | std::vector{1, 2, 3})); 52 | 53 | CHECK(rsl::subset_of(Parameter("", std::vector{true, false}), 54 | std::vector{true, false})); 55 | CHECK(rsl::subset_of(Parameter("", std::vector{}), std::vector{true, false})); 56 | CHECK(!rsl::subset_of(Parameter("", std::vector{false, false}), 57 | std::vector{true})); 58 | 59 | auto const result = 60 | rsl::subset_of(Parameter("test", std::vector{"foo", "foo"}), 61 | std::vector{"", "1", "2", "three"}); 62 | REQUIRE(!result); 63 | CHECK(result.error() == "Entry 'foo' in parameter 'test' is not in the set '{, 1, 2, three}'"); 64 | } 65 | 66 | TEST_CASE("rsl::fixed_size") { 67 | CHECK(rsl::fixed_size(Parameter("", "foo"), 3)); 68 | CHECK(rsl::fixed_size(Parameter("", ""), 0)); 69 | CHECK(!rsl::fixed_size(Parameter("", "foo"), 0)); 70 | CHECK(!rsl::fixed_size(Parameter("", "foo"), 5)); 71 | 72 | CHECK(rsl::fixed_size(Parameter("", std::vector{"", "1", "2"}), 3)); 73 | CHECK(rsl::fixed_size(Parameter("", std::vector{}), 0)); 74 | CHECK(!rsl::fixed_size(Parameter("", std::vector{"foo", "foo"}), 3)); 75 | 76 | CHECK(rsl::fixed_size(Parameter("", std::vector{1.0, 2.2, 1.1}), 3)); 77 | CHECK(rsl::fixed_size(Parameter("", std::vector{}), 0)); 78 | CHECK(!rsl::fixed_size(Parameter("", std::vector{1.1, 1.1}), 3)); 79 | 80 | CHECK(rsl::fixed_size(Parameter("", std::vector{1, 2, 3}), 3)); 81 | CHECK(rsl::fixed_size(Parameter("", std::vector{}), 0)); 82 | CHECK(!rsl::fixed_size(Parameter("", std::vector{-1, -1}), 0)); 83 | 84 | CHECK(rsl::fixed_size(Parameter("", std::vector{true, false}), 2)); 85 | CHECK(rsl::fixed_size(Parameter("", std::vector{}), 0)); 86 | CHECK(!rsl::fixed_size(Parameter("", std::vector{false, false}), 0)); 87 | 88 | auto const result = rsl::fixed_size(Parameter("test", "foo"), 0); 89 | REQUIRE(!result); 90 | CHECK(result.error() == "Length of parameter 'test' is '3' but must be equal to '0'"); 91 | } 92 | 93 | TEST_CASE("rsl::size_gt") { 94 | CHECK(rsl::size_gt(Parameter("", "foo"), 2)); 95 | CHECK(!rsl::size_gt(Parameter("", ""), 0)); 96 | CHECK(!rsl::size_gt(Parameter("", "foo"), 5)); 97 | 98 | CHECK(rsl::size_gt(Parameter("", std::vector{"", "1", "2"}), 1)); 99 | CHECK(!rsl::size_gt(Parameter("", std::vector{}), 0)); 100 | CHECK(!rsl::size_gt(Parameter("", std::vector{"foo", "foo"}), 3)); 101 | 102 | CHECK(rsl::size_gt(Parameter("", std::vector{1.0, 2.2, 1.1}), 2)); 103 | CHECK(!rsl::size_gt(Parameter("", std::vector{}), 0)); 104 | CHECK(!rsl::size_gt(Parameter("", std::vector{1.1, 1.1}), 3)); 105 | 106 | CHECK(rsl::size_gt(Parameter("", std::vector{1, 2, 3}), 2)); 107 | CHECK(!rsl::size_gt(Parameter("", std::vector{}), 0)); 108 | CHECK(rsl::size_gt(Parameter("", std::vector{-1, -1}), 0)); 109 | 110 | CHECK(rsl::size_gt(Parameter("", std::vector{true, false}), 0)); 111 | CHECK(!rsl::size_gt(Parameter("", std::vector{}), 0)); 112 | CHECK(rsl::size_gt(Parameter("", std::vector{false, false}), 0)); 113 | 114 | auto const result = rsl::size_gt(Parameter("test", ""), 0); 115 | REQUIRE(!result); 116 | CHECK(result.error() == "Length of parameter 'test' is '0' but must be greater than '0'"); 117 | } 118 | 119 | TEST_CASE("rsl::size_lt") { 120 | CHECK(rsl::size_lt(Parameter("", "foo"), 5)); 121 | CHECK(!rsl::size_lt(Parameter("", ""), 0)); 122 | CHECK(!rsl::size_lt(Parameter("", "foo"), 3)); 123 | 124 | CHECK(!rsl::size_lt(Parameter("", std::vector{"", "1", "2"}), 1)); 125 | CHECK(!rsl::size_lt(Parameter("", std::vector{}), 0)); 126 | CHECK(rsl::size_lt(Parameter("", std::vector{"foo", "foo"}), 3)); 127 | 128 | CHECK(!rsl::size_lt(Parameter("", std::vector{1.0, 2.2, 1.1}), 2)); 129 | CHECK(!rsl::size_lt(Parameter("", std::vector{}), 0)); 130 | CHECK(rsl::size_lt(Parameter("", std::vector{1.1, 1.1}), 3)); 131 | 132 | CHECK(!rsl::size_lt(Parameter("", std::vector{1, 2, 3}), 2)); 133 | CHECK(!rsl::size_lt(Parameter("", std::vector{}), 0)); 134 | CHECK(!rsl::size_lt(Parameter("", std::vector{-1, -1}), 0)); 135 | 136 | CHECK(rsl::size_lt(Parameter("", std::vector{true, false}), 3)); 137 | CHECK(!rsl::size_lt(Parameter("", std::vector{}), 0)); 138 | CHECK(!rsl::size_lt(Parameter("", std::vector{false, false}), 0)); 139 | 140 | auto const result = rsl::size_lt(Parameter("test", "foo"), 3); 141 | REQUIRE(!result); 142 | CHECK(result.error() == "Length of parameter 'test' is '3' but must be less than '3'"); 143 | } 144 | 145 | TEST_CASE("rsl::not_empty") { 146 | CHECK(rsl::not_empty(Parameter("", "foo"))); 147 | CHECK(!rsl::not_empty(Parameter("", ""))); 148 | 149 | CHECK(rsl::not_empty(Parameter("", std::vector{"", "1", "2"}))); 150 | CHECK(!rsl::not_empty(Parameter("", std::vector{}))); 151 | 152 | CHECK(rsl::not_empty(Parameter("", std::vector{1.0, 2.2, 1.1}))); 153 | CHECK(!rsl::not_empty(Parameter("", std::vector{}))); 154 | 155 | CHECK(rsl::not_empty(Parameter("", std::vector{1, 2, 3}))); 156 | CHECK(!rsl::not_empty(Parameter("", std::vector{}))); 157 | 158 | CHECK(rsl::not_empty(Parameter("", std::vector{true, false}))); 159 | CHECK(!rsl::not_empty(Parameter("", std::vector{}))); 160 | 161 | auto const result = rsl::not_empty(Parameter("test", "")); 162 | REQUIRE(!result); 163 | CHECK(result.error() == "Parameter 'test' cannot be empty"); 164 | } 165 | 166 | TEST_CASE("rsl::element_bounds") { 167 | CHECK(rsl::element_bounds(Parameter("", std::vector{1.0, 2.2, 1.1}), 0.0, 3.0)); 168 | CHECK(rsl::element_bounds(Parameter("", std::vector{}), 0.0, 1.0)); 169 | 170 | CHECK(rsl::element_bounds(Parameter("", std::vector{1, 2, 3}), 0, 5)); 171 | CHECK(!rsl::element_bounds(Parameter("", std::vector{1, 2, 3}), -5, 0)); 172 | CHECK(rsl::element_bounds(Parameter("", std::vector{}), 0, 1)); 173 | 174 | CHECK(rsl::element_bounds(Parameter("", std::vector{true, false}), false, true)); 175 | CHECK(!rsl::element_bounds(Parameter("", std::vector{true, false}), true, false)); 176 | CHECK(!rsl::element_bounds(Parameter("", std::vector{true, false}), false, false)); 177 | CHECK(rsl::element_bounds(Parameter("", std::vector{}), true, false)); 178 | 179 | auto const result = 180 | rsl::element_bounds(Parameter("test", std::vector{1, 2, 3}), -5, 0); 181 | REQUIRE(!result); 182 | CHECK(result.error() == "Value '1' in parameter 'test' must be within bounds '[-5, 0]'"); 183 | } 184 | 185 | TEST_CASE("rsl::lower_element_bounds") { 186 | CHECK( 187 | rsl::lower_element_bounds(Parameter("", std::vector{1.0, 2.2, 1.1}), 0.0)); 188 | CHECK(rsl::lower_element_bounds(Parameter("", std::vector{}), 0.0)); 189 | 190 | CHECK(rsl::lower_element_bounds(Parameter("", std::vector{1, 2, 3}), 0)); 191 | CHECK(!rsl::lower_element_bounds(Parameter("", std::vector{1, 2, 3}), 3)); 192 | CHECK(rsl::lower_element_bounds(Parameter("", std::vector{}), 0)); 193 | 194 | CHECK(rsl::lower_element_bounds(Parameter("", std::vector{true, false}), false)); 195 | CHECK(!rsl::lower_element_bounds(Parameter("", std::vector{true, false}), true)); 196 | CHECK(rsl::lower_element_bounds(Parameter("", std::vector{true, false}), false)); 197 | CHECK(rsl::lower_element_bounds(Parameter("", std::vector{}), true)); 198 | 199 | auto const result = 200 | rsl::lower_element_bounds(Parameter("test", std::vector{1, 2, 3}), 3); 201 | REQUIRE(!result); 202 | CHECK(result.error() == "Value '1' in parameter 'test' must be above lower bound of '3'"); 203 | } 204 | 205 | TEST_CASE("rsl::upper_element_bounds") { 206 | CHECK( 207 | !rsl::upper_element_bounds(Parameter("", std::vector{1.0, 2.2, 1.1}), 0.0)); 208 | CHECK(rsl::upper_element_bounds(Parameter("", std::vector{}), 0.0)); 209 | 210 | CHECK(!rsl::upper_element_bounds(Parameter("", std::vector{1, 2, 3}), 0)); 211 | CHECK(rsl::upper_element_bounds(Parameter("", std::vector{1, 2, 3}), 3)); 212 | CHECK(rsl::upper_element_bounds(Parameter("", std::vector{}), 0)); 213 | 214 | CHECK(!rsl::upper_element_bounds(Parameter("", std::vector{true, false}), false)); 215 | CHECK(rsl::upper_element_bounds(Parameter("", std::vector{true, false}), true)); 216 | CHECK(!rsl::upper_element_bounds(Parameter("", std::vector{true, false}), false)); 217 | CHECK(rsl::upper_element_bounds(Parameter("", std::vector{}), true)); 218 | 219 | auto const result = rsl::upper_element_bounds( 220 | Parameter("test", std::vector{1.0, 2.2, 1.1}), 0.0); 221 | REQUIRE(!result); 222 | CHECK(result.error() == "Value '1' in parameter 'test' must be below upper bound of '0'"); 223 | } 224 | 225 | TEST_CASE("rsl::bounds") { 226 | CHECK(rsl::bounds(Parameter("", 1.0), 1.0, 5.0)); 227 | CHECK(rsl::bounds(Parameter("", 4.3), 1.0, 5.0)); 228 | CHECK(rsl::bounds(Parameter("", 5.0), 1.0, 5.0)); 229 | CHECK(!rsl::bounds(Parameter("", -4.3), 1.0, 5.0)); 230 | CHECK(!rsl::bounds(Parameter("", 10.2), 1.0, 5.0)); 231 | 232 | CHECK(rsl::bounds(Parameter("", 1), 1, 5)); 233 | CHECK(rsl::bounds(Parameter("", 4), 1, 5)); 234 | CHECK(rsl::bounds(Parameter("", 5), 1, 5)); 235 | CHECK(!rsl::bounds(Parameter("", -4), 1, 5)); 236 | CHECK(!rsl::bounds(Parameter("", 10), 1, 5)); 237 | 238 | CHECK(rsl::bounds(Parameter("", true), false, true)); 239 | CHECK(!rsl::bounds(Parameter("", false), true, true)); 240 | CHECK(!rsl::bounds(Parameter("", true), false, false)); 241 | 242 | CHECK_THROWS(rsl::bounds(Parameter("", ""), 1, 5)); 243 | CHECK_THROWS(rsl::bounds(Parameter("", 4), "", "foo")); 244 | 245 | auto const result = rsl::bounds(Parameter("test", -4.3), 1.0, 5.0); 246 | REQUIRE(!result); 247 | CHECK(result.error() == 248 | "Parameter 'test' with the value '-4.3' must be within bounds '[1, 5]'"); 249 | } 250 | 251 | TEST_CASE("rsl::lt") { 252 | CHECK(!rsl::lt(Parameter("", 2.0), 2.0)); 253 | CHECK(!rsl::lt(Parameter("", 4.3), 1.0)); 254 | CHECK(rsl::lt(Parameter("", -4.3), 1.0)); 255 | 256 | CHECK(!rsl::lt(Parameter("", 1), 1)); 257 | CHECK(!rsl::lt(Parameter("", 4), 1)); 258 | CHECK(rsl::lt(Parameter("", -4), 1)); 259 | 260 | CHECK(!rsl::lt(Parameter("", true), false)); 261 | CHECK(rsl::lt(Parameter("", false), true)); 262 | CHECK(!rsl::lt(Parameter("", true), true)); 263 | 264 | auto const result = rsl::lt(Parameter("test", 4.3), 1.0); 265 | REQUIRE(!result); 266 | CHECK(result.error() == "Parameter 'test' with the value '4.3' must be less than '1'"); 267 | } 268 | 269 | TEST_CASE("rsl::gt") { 270 | CHECK(!rsl::gt(Parameter("", 2.0), 2.0)); 271 | CHECK(rsl::gt(Parameter("", 4.3), 1.0)); 272 | CHECK(!rsl::gt(Parameter("", -4.3), 1.0)); 273 | 274 | CHECK(!rsl::gt(Parameter("", 1), 1)); 275 | CHECK(rsl::gt(Parameter("", 4), 1)); 276 | CHECK(!rsl::gt(Parameter("", -4), 1)); 277 | 278 | CHECK(rsl::gt(Parameter("", true), false)); 279 | CHECK(!rsl::gt(Parameter("", false), true)); 280 | CHECK(!rsl::gt(Parameter("", true), true)); 281 | 282 | auto const result = rsl::gt(Parameter("test", 1), 1); 283 | REQUIRE(!result); 284 | CHECK(result.error() == "Parameter 'test' with the value '1' must be greater than '1'"); 285 | } 286 | 287 | TEST_CASE("rsl::lt_eq") { 288 | CHECK(rsl::lt_eq(Parameter("", 2.0), 2.0)); 289 | CHECK(!rsl::lt_eq(Parameter("", 4.3), 1.0)); 290 | CHECK(rsl::lt_eq(Parameter("", -4.3), 1.0)); 291 | 292 | CHECK(rsl::lt_eq(Parameter("", 1), 1)); 293 | CHECK(!rsl::lt_eq(Parameter("", 4), 1)); 294 | CHECK(rsl::lt_eq(Parameter("", -4), 1)); 295 | 296 | CHECK(!rsl::lt_eq(Parameter("", true), false)); 297 | CHECK(rsl::lt_eq(Parameter("", false), true)); 298 | CHECK(rsl::lt_eq(Parameter("", true), true)); 299 | 300 | auto const result = rsl::lt_eq(Parameter("test", 4.3), 1.0); 301 | REQUIRE(!result); 302 | CHECK(result.error() == 303 | "Parameter 'test' with the value '4.3' must be less than or equal to '1'"); 304 | } 305 | 306 | TEST_CASE("rsl::gt_eq") { 307 | CHECK(rsl::gt_eq(Parameter("", 2.0), 2.0)); 308 | CHECK(rsl::gt_eq(Parameter("", 4.3), 1.0)); 309 | CHECK(!rsl::gt_eq(Parameter("", -4.3), 1.0)); 310 | 311 | CHECK(rsl::gt_eq(Parameter("", 1), 1)); 312 | CHECK(rsl::gt_eq(Parameter("", 4), 1)); 313 | CHECK(!rsl::gt_eq(Parameter("", -4), 1)); 314 | 315 | CHECK(rsl::gt_eq(Parameter("", true), false)); 316 | CHECK(!rsl::gt_eq(Parameter("", false), true)); 317 | CHECK(rsl::gt_eq(Parameter("", true), true)); 318 | 319 | auto const result = rsl::gt_eq(Parameter("test", -4.3), 1.0); 320 | REQUIRE(!result); 321 | CHECK(result.error() == 322 | "Parameter 'test' with the value '-4.3' must be greater than or equal to '1'"); 323 | } 324 | 325 | TEST_CASE("rsl::one_of") { 326 | CHECK(rsl::one_of(Parameter("", 2.0), std::vector{2.0})); 327 | CHECK(rsl::one_of(Parameter("", 2.0), std::vector{1.0, 2.0, 3.5})); 328 | CHECK(!rsl::one_of(Parameter("", 0.0), std::vector{1.0, 2.0, 3.5})); 329 | CHECK(!rsl::one_of(Parameter("", 0.0), std::vector{})); 330 | 331 | CHECK(rsl::one_of(Parameter("", 1), std::vector{1})); 332 | CHECK(rsl::one_of(Parameter("", 1), std::vector{1, 2, 3, 4})); 333 | CHECK(!rsl::one_of(Parameter("", 0), std::vector{1, 2, 3, 4})); 334 | CHECK(!rsl::one_of(Parameter("", 0), std::vector{})); 335 | 336 | CHECK(!rsl::one_of(Parameter("", true), std::vector{false})); 337 | CHECK(rsl::one_of(Parameter("", true), std::vector{false, true})); 338 | CHECK(rsl::one_of(Parameter("", true), std::vector{true})); 339 | CHECK(!rsl::one_of(Parameter("", true), std::vector{})); 340 | 341 | CHECK(rsl::one_of(Parameter("", "foo"), std::vector{"foo", "baz"})); 342 | CHECK(!rsl::one_of(Parameter("", ""), std::vector{"foo", "baz"})); 343 | 344 | auto const result = 345 | rsl::one_of(Parameter("test", 0.0), std::vector{1.0, 2.0, 3.5}); 346 | REQUIRE(!result); 347 | CHECK(result.error() == "Parameter 'test' with the value '0' is not in the set '{1, 2, 3.5}'"); 348 | } 349 | 350 | TEST_CASE("rsl::to_parameter_result_msg") { 351 | SECTION("Has value") { 352 | auto const parameter_result = rsl::to_parameter_result_msg({}); 353 | CHECK(parameter_result.successful); 354 | CHECK(parameter_result.reason.empty()); 355 | } 356 | 357 | SECTION("Has error") { 358 | auto const parameter_result = rsl::to_parameter_result_msg(tl::unexpected("test"s)); 359 | CHECK(!parameter_result.successful); 360 | CHECK(parameter_result.reason == "test"); 361 | } 362 | } 363 | -------------------------------------------------------------------------------- /tests/queue.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std::chrono_literals; 11 | 12 | // NOLINTBEGIN(readability-container-size-empty) 13 | 14 | TEST_CASE("rsl::Queue") { 15 | SECTION("Type traits") { 16 | STATIC_CHECK(!std::is_copy_constructible_v>); 17 | STATIC_CHECK(!std::is_copy_assignable_v>); 18 | STATIC_CHECK(!std::is_nothrow_move_constructible_v>); 19 | STATIC_CHECK(!std::is_nothrow_move_assignable_v>); 20 | } 21 | 22 | SECTION("Default constructor") { 23 | auto const queue = rsl::Queue(); 24 | CHECK(queue.size() == 0); 25 | CHECK(queue.empty()); 26 | } 27 | 28 | SECTION("push()") { 29 | auto queue = rsl::Queue(); 30 | queue.push(42); 31 | CHECK(queue.size() == 1); 32 | CHECK(!queue.empty()); 33 | 34 | for (int i = 0; i < 99; ++i) queue.push(i); 35 | CHECK(queue.size() == 100); 36 | CHECK(!queue.empty()); 37 | } 38 | 39 | SECTION("clear()") { 40 | auto queue = rsl::Queue(); 41 | for (int i = 0; i < 100; ++i) queue.push(i); 42 | CHECK(queue.size() == 100); 43 | CHECK(!queue.empty()); 44 | queue.clear(); 45 | CHECK(queue.size() == 0); 46 | CHECK(queue.empty()); 47 | } 48 | 49 | SECTION("pop()") { 50 | SECTION("Sequentially") { 51 | auto queue = rsl::Queue(); 52 | 53 | CHECK(!queue.pop().has_value()); 54 | queue.push(999); 55 | queue.push(1000); 56 | 57 | CHECK(queue.pop(-1ms).value() == 999); 58 | CHECK(queue.size() == 1); 59 | 60 | CHECK(queue.pop().value() == 1000); 61 | CHECK(queue.size() == 0); 62 | 63 | CHECK(!queue.pop(1ms).has_value()); 64 | CHECK(queue.size() == 0); 65 | } 66 | 67 | SECTION("Concurrently") { 68 | auto queue = rsl::Queue(); 69 | 70 | constexpr auto thread_count = size_t(10); 71 | constexpr auto item_count = size_t(100); 72 | 73 | auto producers = std::array(); 74 | for (auto& producer : producers) { 75 | producer = std::thread([&queue] { 76 | for (size_t i = 0; i < item_count; ++i) queue.push(int(i)); 77 | }); 78 | } 79 | 80 | auto items_removed = std::atomic(0); 81 | auto consumers = std::array(); 82 | for (auto& consumer : consumers) { 83 | consumer = std::thread([&queue, &items_removed] { 84 | for (size_t i = 0; i < item_count; ++i) 85 | if (queue.pop(1ms).has_value()) ++items_removed; 86 | }); 87 | } 88 | 89 | for (auto& producer : producers) producer.join(); 90 | for (auto& consumer : consumers) consumer.join(); 91 | 92 | CHECK(queue.size() == thread_count * item_count - items_removed); 93 | } 94 | } 95 | } 96 | 97 | // NOLINTEND(readability-container-size-empty) 98 | -------------------------------------------------------------------------------- /tests/random.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace { 10 | auto const* const rng = &rsl::rng({0, 1}); 11 | } 12 | 13 | TEST_CASE("rsl::rng") { 14 | SECTION("Repeated calls in thread yield same object") { 15 | CHECK(rng == &rsl::rng()); 16 | CHECK(rng == &rsl::rng()); 17 | CHECK(rng == &rsl::rng()); 18 | } 19 | 20 | SECTION("Calls from separate threads yield separate objects") { 21 | // Test using randomly generated seed 22 | auto thread1 = std::thread([] { CHECK(rng != &rsl::rng()); }); 23 | 24 | // Test that custom seed in separate thread does not throw 25 | auto thread2 = std::thread([] { CHECK(rng != &rsl::rng({2, 3})); }); 26 | 27 | thread1.join(); 28 | thread2.join(); 29 | } 30 | 31 | SECTION("Throw if seeded after first call") { 32 | CHECK_NOTHROW(rsl::rng({})); 33 | CHECK_THROWS_WITH(rsl::rng({1, 2, 3, 4}), Catch::Matchers::ContainsSubstring( 34 | "rng cannot be re-seeded on this thread")); 35 | } 36 | } 37 | 38 | TEST_CASE("rsl::uniform_real") { 39 | constexpr auto lower = -100.; 40 | constexpr auto upper = 100.; 41 | for (int i = 0; i < 1'000; ++i) { 42 | auto const value = rsl::uniform_real(lower, upper); 43 | CHECK(value >= lower); 44 | CHECK(value < upper); 45 | } 46 | } 47 | 48 | TEST_CASE("rsl::uniform_int") { 49 | constexpr auto lower = -100; 50 | constexpr auto upper = 100; 51 | for (int i = 0; i < 1'000; ++i) { 52 | auto const value = rsl::uniform_int(lower, upper); 53 | CHECK(value >= lower); 54 | CHECK(value <= upper); 55 | } 56 | } 57 | 58 | TEST_CASE("rsl::random_unit_quaternion") { 59 | for (int i = 0; i < 1'000; ++i) 60 | CHECK(rsl::random_unit_quaternion().norm() == Catch::Approx(1.).epsilon(0).margin(1e-6)); 61 | } 62 | -------------------------------------------------------------------------------- /tests/static_string.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | using namespace std::literals; 6 | 7 | TEST_CASE("rsl::StaticString") { 8 | SECTION("Type traits") { 9 | STATIC_CHECK(std::is_copy_constructible_v>); 10 | STATIC_CHECK(std::is_copy_assignable_v>); 11 | STATIC_CHECK(std::is_nothrow_move_constructible_v>); 12 | STATIC_CHECK(std::is_nothrow_move_assignable_v>); 13 | } 14 | 15 | SECTION("Construction") { 16 | SECTION("Default constructor") { 17 | auto const static_string = rsl::StaticString<10>(); 18 | CHECK(static_string.begin() == static_string.end()); 19 | } 20 | 21 | SECTION("Collection constructor") { 22 | auto const string = "Hello, world!"s; 23 | auto const static_string = rsl::StaticString<14>(string); 24 | CHECK(static_string.begin() != static_string.end()); 25 | auto begin = static_string.begin(); // NOLINT(readability-qualified-auto) 26 | CHECK(*begin++ == 'H'); 27 | CHECK(*begin++ == 'e'); 28 | CHECK(*begin++ == 'l'); 29 | CHECK(*begin++ == 'l'); 30 | CHECK(*begin++ == 'o'); 31 | CHECK(*begin++ == ','); 32 | CHECK(*begin++ == ' '); 33 | CHECK(*begin++ == 'w'); 34 | CHECK(*begin++ == 'o'); 35 | CHECK(*begin++ == 'r'); 36 | CHECK(*begin++ == 'l'); 37 | CHECK(*begin++ == 'd'); 38 | } 39 | } 40 | 41 | SECTION("begin()") { 42 | auto const static_string = rsl::StaticString<5>("foo"); 43 | CHECK(static_string.begin() == std::begin(static_string)); 44 | CHECK(static_string.begin() == std::cbegin(static_string)); 45 | CHECK(*static_string.begin() == 'f'); 46 | } 47 | 48 | SECTION("end()") { 49 | auto const static_string = rsl::StaticString<5>("bar"); 50 | CHECK(static_string.end() == std::end(static_string)); 51 | CHECK(static_string.end() == std::cend(static_string)); 52 | } 53 | 54 | SECTION("std::string_view()") { 55 | auto const static_vector = rsl::StaticString<16>("PickNik Robotics"); 56 | auto const string_view = std::string_view(static_vector); 57 | CHECK(string_view[0] == 'P'); 58 | CHECK(string_view[1] == 'i'); 59 | CHECK(string_view[2] == 'c'); 60 | CHECK(string_view[3] == 'k'); 61 | CHECK(string_view[4] == 'N'); 62 | CHECK(string_view[5] == 'i'); 63 | CHECK(string_view[6] == 'k'); 64 | CHECK(string_view[7] == ' '); 65 | CHECK(string_view[8] == 'R'); 66 | CHECK(string_view[9] == 'o'); 67 | CHECK(string_view[10] == 'b'); 68 | CHECK(string_view[11] == 'o'); 69 | CHECK(string_view[12] == 't'); 70 | CHECK(string_view[13] == 'i'); 71 | CHECK(string_view[14] == 'c'); 72 | CHECK(string_view[15] == 's'); 73 | } 74 | } 75 | 76 | TEST_CASE("rsl::to_string") { 77 | CHECK(rsl::to_string(rsl::StaticString<0>()).empty()); 78 | CHECK(rsl::to_string(rsl::StaticString<10>("happy"s)) == "happy"s); 79 | } 80 | -------------------------------------------------------------------------------- /tests/static_vector.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | TEMPLATE_TEST_CASE("rsl::StaticVector", "", int, float) { 6 | SECTION("Type traits") { 7 | STATIC_CHECK(std::is_copy_constructible_v>); 8 | STATIC_CHECK(std::is_copy_assignable_v>); 9 | STATIC_CHECK(std::is_nothrow_move_constructible_v>); 10 | STATIC_CHECK(std::is_nothrow_move_assignable_v>); 11 | } 12 | 13 | SECTION("Construction") { 14 | SECTION("Default constructor") { 15 | auto const static_vector = rsl::StaticVector(); 16 | CHECK(static_vector.begin() == static_vector.end()); 17 | } 18 | 19 | SECTION("Collection constructor") { 20 | auto const vector = std::vector{1, 2, 3, 4, 5}; 21 | auto const static_vector = rsl::StaticVector(vector); 22 | CHECK(static_vector.begin() != static_vector.end()); 23 | TestType i = 1; 24 | for (auto const& number : static_vector) { 25 | CHECK(number == i); 26 | i += 1; 27 | } 28 | } 29 | 30 | SECTION("Initializer list constructor") { 31 | auto const static_vector = rsl::StaticVector{5, 4, 3, 2, 1}; 32 | CHECK(static_vector.begin() != static_vector.end()); 33 | TestType i = 5; 34 | for (auto const& number : static_vector) { 35 | CHECK(number == i); 36 | i -= 1; 37 | } 38 | } 39 | } 40 | 41 | SECTION("begin()") { 42 | SECTION("Non-const") { 43 | auto static_vector = rsl::StaticVector{5, 4, 3, 2, 1}; 44 | CHECK(static_vector.begin() == std::begin(static_vector)); 45 | CHECK(static_vector.begin() == std::cbegin(static_vector)); 46 | CHECK(++*static_vector.begin() == 6); 47 | } 48 | 49 | SECTION("Const") { 50 | auto const static_vector = rsl::StaticVector{5, 4, 3, 2, 1}; 51 | CHECK(static_vector.begin() == std::begin(static_vector)); 52 | CHECK(static_vector.begin() == std::cbegin(static_vector)); 53 | CHECK(*static_vector.begin() == 5); 54 | } 55 | } 56 | 57 | SECTION("end()") { 58 | SECTION("Non-const") { 59 | auto static_vector = rsl::StaticVector{10, 9, 8, 7, 6}; 60 | CHECK(static_vector.end() == std::end(static_vector)); 61 | CHECK(static_vector.end() == std::cend(static_vector)); 62 | CHECK(--*(static_vector.end() - 1) == 5); 63 | } 64 | 65 | SECTION("Const") { 66 | auto const static_vector = rsl::StaticVector{5, 4, 3, 2, 1}; 67 | CHECK(static_vector.end() == std::end(static_vector)); 68 | CHECK(static_vector.end() == std::cend(static_vector)); 69 | CHECK(*(static_vector.end() - 1) == 1); 70 | } 71 | } 72 | 73 | SECTION("User defined conversions") { 74 | SECTION("tcb::span()") { 75 | auto static_vector = rsl::StaticVector{11, 12, 13, 14, 15}; 76 | auto const span = tcb::span(static_vector); 77 | CHECK(span[0] == 11); 78 | CHECK(span[1] == 12); 79 | CHECK(span[2] == 13); 80 | CHECK(span[3] == 14); 81 | CHECK(span[4] == 15); 82 | } 83 | 84 | SECTION("tcb::span()") { 85 | auto const static_vector = rsl::StaticVector{20, 19, 18, 17, 16}; 86 | auto const span = tcb::span(static_vector); 87 | CHECK(span[0] == 20); 88 | CHECK(span[1] == 19); 89 | CHECK(span[2] == 18); 90 | CHECK(span[3] == 17); 91 | CHECK(span[4] == 16); 92 | } 93 | } 94 | } 95 | 96 | TEST_CASE("rsl::to_vector") { 97 | CHECK(rsl::to_vector(rsl::StaticVector{}).empty()); 98 | CHECK(rsl::to_vector(rsl::StaticVector{1, 2, 3, 4, 5}) == std::vector{1, 2, 3, 4, 5}); 99 | } 100 | -------------------------------------------------------------------------------- /tests/strong_type.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | TEST_CASE("rsl::StrongType") { 6 | using StrongInt = rsl::StrongType; // For testing constexpr support 7 | using StrongString = 8 | rsl::StrongType; // For testing non-constexpr types 9 | 10 | SECTION("Type traits") { 11 | STATIC_CHECK(std::is_copy_constructible_v); 12 | STATIC_CHECK(std::is_copy_assignable_v); 13 | STATIC_CHECK(std::is_nothrow_move_constructible_v); 14 | STATIC_CHECK(std::is_nothrow_move_assignable_v); 15 | } 16 | 17 | SECTION("Construction") { 18 | constexpr auto strong_int = StrongInt(42); 19 | STATIC_CHECK(strong_int.get() == 42); 20 | STATIC_CHECK(int{strong_int} == 42); 21 | STATIC_CHECK(int(strong_int) == 42); 22 | 23 | auto const strong_string = StrongString("abcdefg"); 24 | CHECK(strong_string.get() == "abcdefg"); 25 | CHECK(std::string{strong_string} == "abcdefg"); 26 | CHECK(std::string(strong_string) == "abcdefg"); 27 | } 28 | 29 | SECTION("get()") { 30 | auto strong_int = StrongInt(1337); 31 | strong_int.get() = 100; 32 | CHECK(strong_int.get() == 100); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/try.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | // Required because try.hpp uses non-standard C++ 8 | #pragma GCC diagnostic ignored "-Wpedantic" 9 | 10 | using ExpectedType = tl::expected; 11 | 12 | namespace { 13 | ExpectedType check_try_macro(ExpectedType const& expected) { 14 | ExpectedType::value_type value = TRY(expected); 15 | return ++value; 16 | } 17 | } // namespace 18 | 19 | TEST_CASE("TRY") { 20 | CHECK(check_try_macro(ExpectedType(42)) == 43); 21 | CHECK(check_try_macro(tl::make_unexpected("Failed!")).error() == "Failed!"); 22 | } 23 | --------------------------------------------------------------------------------