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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -530,14 +530,15 @@ const auto c = C{.f1 = "C++", .f2 = "is", .f4 = "great"};
const auto c2 = rfl::replace(c, a);
```


### Support for containers

#### C++ standard library

reflect-cpp supports the following containers from the C++ standard library:

- `std::array`
- `std::atomic`
- `std::atomic_flag`
- `std::deque`
- `std::chrono::duration`
- `std::filesystem::path`
Expand Down
72 changes: 72 additions & 0 deletions docs/atomic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Atomic variables (`std::atomic` and `std::atomic_flag`)

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.

## Supported atomic types

- `std::atomic<T>`
- `std::atomic_flag` (serialized as a boolean)
- Arrays of atomic types (std::array<T, N> and C-style arrays)
- Aggregate types (structs/NamedTuple) containing atomic fields — each atomic field is handled independently

## Example (writing)

```cpp
struct Stats {
std::atomic<std::uint64_t> bytes_downloaded;
std::atomic<bool> finished;
std::atomic_flag atomic_flag;
};

Stats stats{.bytes_downloaded = 123456789, .finished = true, .atomic_flag = ATOMIC_FLAG_INIT};
const auto json_str = rfl::json::write(stats);
// -> {"bytes_downloaded":123456789,"finished":true,"atomic_flag":false}
```

Note: the exact boolean value for `atomic_flag` depends on whether it is set or cleared.

## Example (reading)

Reading atomic variables is not quite trivial, because atomic fields cannot be copied or moved. Consider the following example:

```cpp
// const auto res = rfl::json::read<Stats>(json_str);
// This will NOT compile because std::atomic<T> is neither copyable nor movable
```

There are two ways around this problem:

### 1. Wrap in `rfl::Ref`, `rfl::Box`, `std::shared_ptr` or `std::unique_ptr`

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.

```cpp
const auto res = rfl::json::read<rfl::Ref<Stats>>(json_str);
```

### 2. Read into a non-atomic representation and then set atomic fields

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`.

```cpp
Stats stats;

const rfl::Result<rfl::Nothing> res =
rfl::json::read<rfl::atomic::remove_atomic_t<Stats>>(json_str)
.transform([&](auto&& non_atomic_stats) {
return rfl::atomic::set_atomic_fields(non_atomic_stats, &stats);
});

if (!res) {
// handle error
std::cerr << "Error reading JSON: " << res.error().what() << std::endl;
}
```

## Limitations and notes

- Structs containing atomic fields must be default-constructible.
- 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).
- The semantics used for setting atomic values use relaxed memory order (`std::memory_order_relaxed`).
- For complex aggregates, `rfl::atomic` will recurse into nested fields and arrays to set atomic members.

4 changes: 4 additions & 0 deletions docs/docs-readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@

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

[Default values](default_val.md) - For defining default values for fields that might be absent during deserialization.

[Atomic types](atomic.md) - For serializing and deserializing atomic types.

## Validation

[Regex patterns](patterns.md) - For requiring that strings follow used-defined regex patterns.
Expand Down
3 changes: 3 additions & 0 deletions include/rfl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@
#include "rfl/always_false.hpp"
#include "rfl/apply.hpp"
#include "rfl/as.hpp"
#include "rfl/atomic/is_atomic.hpp"
#include "rfl/atomic/remove_atomic_t.hpp"
#include "rfl/atomic/set_atomic.hpp"
#include "rfl/comparisons.hpp"
#include "rfl/concepts.hpp"
#include "rfl/default.hpp"
Expand Down
131 changes: 131 additions & 0 deletions include/rfl/atomic/is_atomic.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
#ifndef RFL_ATOMIC_ISATOMIC_HPP_
#define RFL_ATOMIC_ISATOMIC_HPP_

#include <array>
#include <atomic>
#include <type_traits>

#include "../NamedTuple.hpp"
#include "../Tuple.hpp"
#include "../named_tuple_t.hpp"
#include "../to_view.hpp"

namespace rfl::atomic {

template <class T>
struct is_atomic;

template <class T>
struct is_atomic {
static constexpr bool value = false;
using RemoveAtomicT = T;
static void set(RemoveAtomicT&& val, T* _t) { *_t = std::forward<T>(val); };
};

template <class T>
struct is_atomic<std::atomic<T>> {
static constexpr bool value = true;
using RemoveAtomicT = T;
static void set(RemoveAtomicT&& val, std::atomic<T>* _t) {
_t->store(std::forward<RemoveAtomicT>(val), std::memory_order_relaxed);
};
};

template <>
struct is_atomic<std::atomic_flag> {
static constexpr bool value = true;
using RemoveAtomicT = bool;
static void set(RemoveAtomicT&& val, std::atomic_flag* _t) {
if (val) {
_t->test_and_set(std::memory_order_relaxed);
} else {
_t->clear(std::memory_order_relaxed);
}
}
};

template <class T, size_t N>
struct is_atomic<std::array<T, N>> {
using Type = std::remove_cvref_t<T>;

static constexpr bool value = is_atomic<Type>::value;
using RemoveAtomicT = std::array<typename is_atomic<Type>::RemoveAtomicT, N>;
static void set(RemoveAtomicT&& val, std::array<T, N>* _t) {
for (size_t i = 0; i < N; ++i) {
is_atomic<T>::set(
std::forward<typename is_atomic<Type>::RemoveAtomicT>(val[i]),
&((*_t)[i]));
Comment on lines +55 to +57

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

There's an inconsistency in the use of T versus Type (which is std::remove_cvref_t<T>). The set function uses is_atomic<T>::set, but T can have cv-qualifiers, which would cause the base template of is_atomic to be instantiated instead of the correct specialization. This can lead to incorrect behavior or compilation errors. You should consistently use Type, which is the cv-ref removed version of T.

      is_atomic<Type>::set(
          std::forward<typename is_atomic<Type>::RemoveAtomicT>(val[i]),
          &((*_t)[i]));

}
}
};

template <class T, size_t N>
struct is_atomic<T[N]> {
using Type = std::remove_cvref_t<T>;

static constexpr bool value = is_atomic<Type>::value;
using RemoveAtomicT = std::array<typename is_atomic<Type>::RemoveAtomicT, N>;
static void set(RemoveAtomicT&& val, T (*_t)[N]) {
for (size_t i = 0; i < N; ++i) {
is_atomic<T>::set(
std::forward<typename is_atomic<Type>::RemoveAtomicT>(val[i]),
&((*_t)[i]));
}
}
};

template <class... Fields>
struct is_atomic<NamedTuple<Fields...>> {
static constexpr bool value =
(is_atomic<typename Fields::Type>::value || ...);

using RemoveAtomicT = NamedTuple<
rfl::Field<Fields::name_,
typename is_atomic<typename Fields::Type>::RemoveAtomicT>...>;

static void set(RemoveAtomicT&& val, NamedTuple<Fields...>* _t) {
(is_atomic<typename Fields::Type>::set(
std::forward<typename is_atomic<
std::remove_cvref_t<typename Fields::Type>>::RemoveAtomicT>(
val.template get<Fields::name_>()),
&(_t->template get<Fields::name_>())),
...);
}
};

template <class T>
requires(std::is_class_v<T> && std::is_aggregate_v<T>)
struct is_atomic<T> {
static constexpr bool value = is_atomic<named_tuple_t<T>>::value;

using RemoveAtomicT = typename is_atomic<named_tuple_t<T>>::RemoveAtomicT;

static void set(RemoveAtomicT&& val, T* _t) {
using Fields = typename named_tuple_t<T>::Fields;

const auto view = to_view(*_t);

const auto set_field = [&]<size_t _i>(std::integral_constant<size_t, _i>) {
using FieldType = typename rfl::tuple_element_t<_i, Fields>::Type;
using FieldRemoveAtomicT =
typename is_atomic<std::remove_cvref_t<FieldType>>::RemoveAtomicT;

is_atomic<std::remove_cvref_t<FieldType>>::set(
std::forward<FieldRemoveAtomicT>(val.template get<_i>()),
view.template get<_i>());
};

constexpr size_t num_fields = std::remove_cvref_t<decltype(view)>::size();

[&]<size_t... _is>(std::index_sequence<_is...>) {
(set_field(std::integral_constant<size_t, _is>{}), ...);
}(std::make_index_sequence<num_fields>{});
}
};

template <class T>
constexpr bool is_atomic_v = is_atomic<std::remove_cvref_t<T>>::value;

} // namespace rfl::atomic

#endif
16 changes: 16 additions & 0 deletions include/rfl/atomic/remove_atomic_t.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#ifndef RFL_ATOMIC_REMOVE_ATOMIC_T_HPP_
#define RFL_ATOMIC_REMOVE_ATOMIC_T_HPP_

#include <type_traits>

#include "is_atomic.hpp"

namespace rfl::atomic {

template <class T>
using remove_atomic_t =
typename is_atomic<std::remove_cvref_t<T>>::RemoveAtomicT;

} // namespace rfl::atomic

#endif
24 changes: 24 additions & 0 deletions include/rfl/atomic/set_atomic.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#ifndef RFL_ATOMIC_SET_ATOMIC_HPP_
#define RFL_ATOMIC_SET_ATOMIC_HPP_

#include <type_traits>

#include "../Result.hpp"
#include "is_atomic.hpp"

namespace rfl::atomic {

template <class T, class U>
Nothing set_atomic(U&& val, T* _t) {
is_atomic<std::remove_cvref_t<T>>::set(std::forward<U>(val), _t);
return Nothing{};
}

template <class T, class U>
Nothing set_atomic(U&& val, T& _t) {
return set_atomic(std::forward<U>(val), &_t);
}

} // namespace rfl::atomic

#endif
20 changes: 19 additions & 1 deletion include/rfl/internal/has_default_val_v.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#ifndef RFL_HASDEFAULTVALV_HPP_
#define RFL_HASDEFAULTVALV_HPP_

#include <array>
#include <type_traits>

#include "../NamedTuple.hpp"
Expand All @@ -11,6 +13,11 @@ namespace rfl::internal {
template <class T>
struct HasDefaultVal;

template <class T>
struct HasDefaultVal {
static constexpr bool value = false;
};

template <class... Fields>
struct HasDefaultVal<NamedTuple<Fields...>> {
static constexpr bool value =
Expand All @@ -20,7 +27,18 @@ struct HasDefaultVal<NamedTuple<Fields...>> {
};

template <class T>
constexpr bool has_default_val_v = HasDefaultVal<named_tuple_t<T>>::value;
requires(std::is_class_v<T> && std::is_aggregate_v<T>)
struct HasDefaultVal<T> : HasDefaultVal<named_tuple_t<T>> {};

template <class T, size_t N>
struct HasDefaultVal<std::array<T, N>> : HasDefaultVal<std::remove_cvref_t<T>> {
};

template <class T, size_t N>
struct HasDefaultVal<T[N]> : HasDefaultVal<std::remove_cvref_t<T>> {};

template <class T>
constexpr bool has_default_val_v = HasDefaultVal<std::remove_cvref_t<T>>::value;

} // namespace rfl::internal

Expand Down
2 changes: 2 additions & 0 deletions include/rfl/parsing/Parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#define RFL_PARSING_PARSER_HPP_

#include "Parser_array.hpp"
#include "Parser_atomic.hpp"
#include "Parser_atomic_flag.hpp"
#include "Parser_base.hpp"
#include "Parser_basic_type.hpp"
#include "Parser_box.hpp"
Expand Down
39 changes: 39 additions & 0 deletions include/rfl/parsing/Parser_atomic.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#ifndef RFL_PARSING_PARSER_ATOMIC_HPP_
#define RFL_PARSING_PARSER_ATOMIC_HPP_

#include <atomic>
#include <map>
#include <type_traits>

#include "../DefaultVal.hpp"
#include "AreReaderAndWriter.hpp"
#include "Parent.hpp"
#include "Parser_base.hpp"
#include "schema/Type.hpp"

namespace rfl::parsing {

template <class R, class W, class T, class ProcessorsType>
requires AreReaderAndWriter<R, W, std::atomic<T>>
struct Parser<R, W, std::atomic<T>, ProcessorsType> {
using InputVarType = typename R::InputVarType;

/// Read is not supported for atomic types - we must used rfl::atomic instead.

template <class P>
static void write(const W& _w, const std::atomic<T>& _a, const P& _parent) {
Parser<R, W, std::remove_cvref_t<T>, ProcessorsType>::write(
_w, _a.load(std::memory_order_relaxed), _parent);
}

static schema::Type to_schema(
std::map<std::string, schema::Type>* _definitions) {
using U = std::remove_cvref_t<T>;
return schema::Type{
Parser<R, W, U, ProcessorsType>::to_schema(_definitions)};
}
};

} // namespace rfl::parsing

#endif
Loading
Loading