Skip to content

Commit 955c934

Browse files
authored
Merge branch 'main' into main
2 parents 9571235 + e29d43e commit 955c934

33 files changed

Lines changed: 748 additions & 58 deletions

.github/workflows/linux.yaml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,6 @@ jobs:
3535
- compiler: gcc
3636
compiler-version: 12
3737
cxx: 23
38-
- compiler: gcc
39-
compiler-version: 13
40-
cxx: 23
4138
- compiler: llvm
4239
compiler-version: 16
4340
cxx: 23

.github/workflows/macos.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
strategy:
1111
fail-fast: false
1212
matrix:
13-
os: ["macos-latest", "macos-13"]
13+
os: ["macos-latest", "macos-15-intel"]
1414
format: ["tests", "benchmarks"]
1515
name: "${{ matrix.os }} (${{ matrix.format }})"
1616
concurrency:

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
*.toml
5555
*.ubjson
5656
*.xml
57+
!package.xml
5758
*.yml
5859
*.yaml
5960

@@ -94,4 +95,4 @@ share/python-wheels/
9495
MANIFEST
9596

9697
# Docs
97-
site/
98+
site/

CMakeLists.txt

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ endif()
143143

144144
project(reflectcpp VERSION 0.23.0 LANGUAGES CXX)
145145

146+
find_package(ament_cmake QUIET)
147+
146148
if(PROJECT_IS_TOP_LEVEL)
147149
set(REFLECTCPP_INSTALL ON)
148150
endif()
@@ -487,12 +489,18 @@ if(REFLECTCPP_INSTALL)
487489
include(CMakePackageConfigHelpers)
488490

489491
configure_package_config_file(reflectcpp-config.cmake.in
490-
${CMAKE_CURRENT_BINARY_DIR}/reflectcpp-config.cmake
491-
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/reflectcpp
492-
)
492+
${CMAKE_CURRENT_BINARY_DIR}/reflectcpp-config.cmake
493+
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/reflectcpp
494+
)
495+
write_basic_package_version_file(
496+
${CMAKE_CURRENT_BINARY_DIR}/reflectcpp-version.cmake
497+
COMPATIBILITY SameMinorVersion
498+
)
493499

494500
install(
495-
FILES "${CMAKE_CURRENT_BINARY_DIR}/reflectcpp-config.cmake"
501+
FILES
502+
"${CMAKE_CURRENT_BINARY_DIR}/reflectcpp-config.cmake"
503+
"${CMAKE_CURRENT_BINARY_DIR}/reflectcpp-version.cmake"
496504
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/reflectcpp"
497505
)
498506

@@ -503,13 +511,14 @@ if(REFLECTCPP_INSTALL)
503511
FILE_SET reflectcpp_headers
504512
TYPE HEADERS
505513
BASE_DIRS $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>
506-
FILES ${RFL_HEADERS})
514+
FILES ${RFL_HEADERS}
515+
)
507516

508517
install(
509518
TARGETS reflectcpp
510519
EXPORT reflectcpp-exports
511520
FILE_SET reflectcpp_headers DESTINATION ${INCLUDE_INSTALL_DIR}
512-
)
521+
)
513522

514523
install(
515524
EXPORT reflectcpp-exports
@@ -546,3 +555,7 @@ set(CPACK_RPM_PACKAGE_REQUIRES "")
546555

547556
include(CPack)
548557

558+
if (ament_cmake_FOUND)
559+
ament_package()
560+
endif()
561+

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -530,14 +530,15 @@ const auto c = C{.f1 = "C++", .f2 = "is", .f4 = "great"};
530530
const auto c2 = rfl::replace(c, a);
531531
```
532532
533-
534533
### Support for containers
535534
536535
#### C++ standard library
537536
538537
reflect-cpp supports the following containers from the C++ standard library:
539538
540539
- `std::array`
540+
- `std::atomic`
541+
- `std::atomic_flag`
541542
- `std::deque`
542543
- `std::chrono::duration`
543544
- `std::filesystem::path`

docs/atomic.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Atomic variables (`std::atomic` and `std::atomic_flag`)
2+
3+
reflect-cpp supports serializing and deserializing atomic types. The library treats atomic wrappers as containers around an underlying value and provides helpers to read plain (non-atomic) representations from input and to set atomic fields afterwards.
4+
5+
## Supported atomic types
6+
7+
- `std::atomic<T>`
8+
- `std::atomic_flag` (serialized as a boolean)
9+
- Arrays of atomic types (std::array<T, N> and C-style arrays)
10+
- Aggregate types (structs/NamedTuple) containing atomic fields — each atomic field is handled independently
11+
12+
## Example (writing)
13+
14+
```cpp
15+
struct Stats {
16+
std::atomic<std::uint64_t> bytes_downloaded;
17+
std::atomic<bool> finished;
18+
std::atomic_flag atomic_flag;
19+
};
20+
21+
Stats stats{.bytes_downloaded = 123456789, .finished = true, .atomic_flag = ATOMIC_FLAG_INIT};
22+
const auto json_str = rfl::json::write(stats);
23+
// -> {"bytes_downloaded":123456789,"finished":true,"atomic_flag":false}
24+
```
25+
26+
Note: the exact boolean value for `atomic_flag` depends on whether it is set or cleared.
27+
28+
## Example (reading)
29+
30+
Reading atomic variables is not quite trivial, because atomic fields cannot be copied or moved. Consider the following example:
31+
32+
```cpp
33+
// const auto res = rfl::json::read<Stats>(json_str);
34+
// This will NOT compile because std::atomic<T> is neither copyable nor movable
35+
```
36+
37+
There are two ways around this problem:
38+
39+
### 1. Wrap in `rfl::Ref`, `rfl::Box`, `std::shared_ptr` or `std::unique_ptr`
40+
41+
The easiest way to read structs with atomic fields is to wrap them in a pointer-like type such as `rfl::Ref`, `rfl::Box`, `std::shared_ptr` or `std::unique_ptr`. This works because the pointer-like types themselves are copyable/movable, even if the underlying type is not.
42+
43+
```cpp
44+
const auto res = rfl::json::read<rfl::Ref<Stats>>(json_str);
45+
```
46+
47+
### 2. Read into a non-atomic representation and then set atomic fields
48+
49+
The second way is to read into a non-atomic representation of the struct and then set the atomic fields afterwards using `rfl::atomic::set_atomic_fields`. The non-atomic representation can be obtained using `rfl::atomic::remove_atomic_t`.
50+
51+
```cpp
52+
Stats stats;
53+
54+
const rfl::Result<rfl::Nothing> res =
55+
rfl::json::read<rfl::atomic::remove_atomic_t<Stats>>(json_str)
56+
.transform([&](auto&& non_atomic_stats) {
57+
return rfl::atomic::set_atomic_fields(non_atomic_stats, &stats);
58+
});
59+
60+
if (!res) {
61+
// handle error
62+
std::cerr << "Error reading JSON: " << res.error().what() << std::endl;
63+
}
64+
```
65+
66+
## Limitations and notes
67+
68+
- Structs containing atomic fields must be default-constructible.
69+
- Atomic types cannot be mixed with `rfl::DefaultVal` or the `rfl::DefaultIfMissing` processor; attempting to do so triggers a static assertion at compile-time (see parser implementations).
70+
- The semantics used for setting atomic values use relaxed memory order (`std::memory_order_relaxed`).
71+
- For complex aggregates, `rfl::atomic` will recurse into nested fields and arrays to set atomic members.
72+

docs/docs-readme.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@
3434

3535
[rfl::Binary, rfl::Hex and rfl::Oct](number_systems.md)- For expressing numbers in different formats.
3636

37+
[Default values](default_val.md) - For defining default values for fields that might be absent during deserialization.
38+
39+
[Atomic types](atomic.md) - For serializing and deserializing atomic types.
40+
3741
## Validation
3842

3943
[Regex patterns](patterns.md) - For requiring that strings follow used-defined regex patterns.

docs/install.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,42 @@ If you want to include all formats supported on Conan, do the following:
128128
```bash
129129
conan build . --build=missing -s compiler.cppstd=gnu20 -o *:with_cbor=True -o *:with_flatbuffers=True -o *:with_msgpack=True -o *:with_toml=True -o *:with_ubjson=True -o *:with_xml=True -o *:with_yaml=True
130130
```
131+
132+
## Option 7: Integrate as a ROS2 package
133+
134+
reflect-cpp now ships with an `ament_cmake` package manifest so it can be
135+
built directly in a ROS 2 workspace and consumed like any other
136+
CMake-config package.
137+
138+
1. Fetch the sources with `vcs` using the `.repos` file from the
139+
tea-ros2 workspace, which already
140+
tracks `reflect-cpp`:
141+
142+
```bash
143+
mkdir -p ~/ros2_ws/src
144+
cd ~/ros2_ws
145+
# adding the entry to your vcs .repos file, or create one
146+
# reflect-cpp:
147+
# type: git
148+
# url: git@github.com:getml/reflect-cpp.git
149+
# version: main
150+
vcs import src < src/.repos
151+
```
152+
153+
2. Build the package with `colcon` (package name: `reflectcpp`). The build
154+
enables installation automatically when invoked from an ament workspace:
155+
156+
```bash
157+
colcon build --packages-select reflectcpp
158+
```
159+
160+
3. Link against reflect-cpp from your ROS 2 package via the exported CMake
161+
config.
162+
163+
```cmake
164+
find_package(reflectcpp REQUIRED)
165+
target_link_libraries(${PROJECT_NAME} reflectcpp::reflectcpp)
166+
```
167+
168+
This makes the `reflectcpp::reflectcpp` target available to your nodes
169+
while keeping the ROS build flow unchanged.

include/rfl.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@
5050
#include "rfl/always_false.hpp"
5151
#include "rfl/apply.hpp"
5252
#include "rfl/as.hpp"
53+
#include "rfl/atomic/is_atomic.hpp"
54+
#include "rfl/atomic/remove_atomic_t.hpp"
55+
#include "rfl/atomic/set_atomic.hpp"
5356
#include "rfl/comparisons.hpp"
5457
#include "rfl/concepts.hpp"
5558
#include "rfl/default.hpp"

include/rfl/Result.hpp

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,19 @@ namespace rfl {
1717
/// Defines the error class to be returned when something went wrong
1818
class Error {
1919
public:
20+
Error() = default;
21+
2022
Error(const std::string& _what) : what_(_what) {}
2123
Error(std::string&& _what) : what_(std::move(_what)) {}
2224

25+
~Error() = default;
26+
2327
Error(const Error& e) = default;
24-
Error(Error&& e) = default;
28+
Error(Error&& e) noexcept = default;
2529

26-
Error& operator=(const Error&) = default;
27-
Error& operator=(Error&&) = default;
30+
Error& operator=(const Error& _other) = default;
31+
32+
Error& operator=(Error&& _other) noexcept = default;
2833

2934
/// Returns the error message, equivalent to .what() in std::exception.
3035
const std::string& what() const& { return what_; }
@@ -450,17 +455,22 @@ template <>
450455
class std::bad_expected_access<rfl::Error> : public bad_expected_access<void> {
451456
public:
452457
explicit constexpr bad_expected_access(rfl::Error er) : err_(std::move(er)) {}
458+
453459
const char* what() const noexcept override { return err_.what().c_str(); }
454460

455-
template <typename Self>
456-
[[nodiscard]]
457-
auto error(this Self&& self) noexcept {
458-
return std::forward<Self>(self).err_;
461+
[[nodiscard]] rfl::Error& error() & noexcept { return err_; }
462+
463+
[[nodiscard]] const rfl::Error& error() const& noexcept { return err_; }
464+
465+
[[nodiscard]] rfl::Error&& error() && noexcept { return std::move(err_); }
466+
467+
[[nodiscard]] const rfl::Error&& error() const&& noexcept {
468+
return std::move(err_);
459469
}
460470

461471
private:
462472
rfl::Error err_;
463473
};
464474
#endif
465475

466-
#endif
476+
#endif

0 commit comments

Comments
 (0)