Skip to content
Open
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
13 changes: 11 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,14 @@ option(REFLECTCPP_CHECK_HEADERS "Make sure that all headers are self-contained"

option(REFLECTCPP_USE_BUNDLED_DEPENDENCIES "Use the bundled dependencies" ON)

option(REFLECTCPP_USE_STD_EXPECTED "Use std::expected instead of the built-in Result type (requires C++-23)" OFF)
option(REFLECTCPP_USE_CPP26_REFLECTION "Use standard C++ reflection (requires C++-26)" OFF)
option(REFLECTCPP_USE_STD_EXPECTED "Use std::expected instead of the built-in Result type (requires C++-23)" ${REFLECTCPP_USE_CPP26_REFLECTION})

if(REFLECTCPP_USE_STD_EXPECTED)
if(REFLECTCPP_USE_CPP26_REFLECTION)
if(NOT DEFINED CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 26)
endif()
elseif(REFLECTCPP_USE_STD_EXPECTED)
if(NOT DEFINED CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 23)
endif()
Expand Down Expand Up @@ -229,6 +234,10 @@ if(REFLECTCPP_BUILD_SHARED)
target_compile_definitions(reflectcpp PUBLIC RFL_BUILD_SHARED)
endif()

if(REFLECTCPP_USE_CPP26_REFLECTION)
target_compile_definitions(reflectcpp PUBLIC REFLECTCPP_USE_CPP26_REFLECTION)
endif()

if(REFLECTCPP_USE_STD_EXPECTED)
target_compile_definitions(reflectcpp PUBLIC REFLECTCPP_USE_STD_EXPECTED)
endif()
Expand Down
45 changes: 33 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@

![image](banner1.png)

**reflect-cpp** is a C++-20 library for **fast serialization, deserialization and validation** using reflection, similar to [pydantic](https://github.com/pydantic/pydantic) in Python, [serde](https://github.com/serde-rs) in Rust, [encoding](https://github.com/golang/go/tree/master/src/encoding) in Go or [aeson](https://github.com/haskell/aeson/tree/master) in Haskell.
**reflect-cpp** is a C++-20/C++-26 library for **fast serialization, deserialization and validation** using reflection, similar to [pydantic](https://github.com/pydantic/pydantic) in Python, [serde](https://github.com/serde-rs) in Rust, [encoding](https://github.com/golang/go/tree/master/src/encoding) in Go or [aeson](https://github.com/haskell/aeson/tree/master) in Haskell.

reflect-cpp supports C++-26 reflection, but most of the functionality is also available in C++-20, except where explicitly noted otherwise.

Moreover, reflect-cpp is the basis for [sqlgen](https://github.com/getml/sqlgen), a **modern, type-safe ORM and SQL query generator** for C++20, inspired by Python's SQLAlchemy/SQLModel and Rust's Diesel. It provides a fluent, composable interface for database operations with compile-time type checking and SQL injection protection.

Expand Down Expand Up @@ -51,6 +53,7 @@ reflect-cpp and sqlgen fill important gaps in C++ development. They reduce boile

### More in our [documentation](https://rfl.getml.com):
- [Installation ↗](https://rfl.getml.com/install/#option-2-compilation-using-cmake)
- [C++26 reflection ↗](https://rfl.getml.com/cpp26_reflection)
- [Benchmarks ↗](https://rfl.getml.com/benchmarks)
- [How to contribute ↗](https://rfl.getml.com/contributing)
- [Compiling and running the tests ↗](https://rfl.getml.com/contributing/#compiling-and-running-the-tests)
Expand All @@ -62,17 +65,17 @@ reflect-cpp provides a unified reflection-based interface across different seria

The following table lists the serialization formats currently supported by reflect-cpp and the underlying libraries used:

| Format | Library | Version | License | Remarks |
|--------------|------------------------------------------------------|--------------|------------| -----------------------------------------------------|
| JSON | [yyjson](https://github.com/ibireme/yyjson) | >= 0.8.0 | MIT | out-of-the-box support, included in this repository |
| Avro | [avro-c](https://avro.apache.org/docs/1.11.1/api/c/) | >= 1.11.3 | Apache 2.0 | Schemaful binary format |
| Format | Library | Version | License | Remarks |
|---------------------|------------------------------------------------------|--------------|------------| -----------------------------------------------------|
| JSON | [yyjson](https://github.com/ibireme/yyjson) | >= 0.8.0 | MIT | out-of-the-box support, included in this repository |
| Avro | [avro-c](https://avro.apache.org/docs/1.11.1/api/c/) | >= 1.11.3 | Apache 2.0 | Schemaful binary format |
| Boost.Serialization | [Boost.Serialization](https://www.boost.org/doc/libs/release/libs/serialization/) | >= 1.74.0 | BSL 1.0 | Streaming binary format with archive interop |
| BSON | [libbson](https://github.com/mongodb/mongo-c-driver) | >= 1.25.1 | Apache 2.0 | JSON-like binary format |
| Cap'n Proto | [capnproto](https://capnproto.org) | >= 1.0.2 | MIT | Schemaful binary format |
| CBOR | [jsoncons](https://github.com/danielaparker/jsoncons)| >= 0.176.0 | BSL 1.0 | JSON-like binary format |
| cli | *(none)* | *(none)* | MIT | Command line interface |
| env | *(none)* | *(none)* | MIT | Environment variables |
| Cereal | [Cereal](https://uscilab.github.io/cereal/) | >= 1.3.2 | BSD | C++ serialization library with multiple formats |
| BSON | [libbson](https://github.com/mongodb/mongo-c-driver) | >= 1.25.1 | Apache 2.0 | JSON-like binary format |
| Cap'n Proto | [capnproto](https://capnproto.org) | >= 1.0.2 | MIT | Schemaful binary format |
| CBOR | [jsoncons](https://github.com/danielaparker/jsoncons)| >= 0.176.0 | BSL 1.0 | JSON-like binary format |
| cli | *(none)* | *(none)* | MIT | Command line interface |
| env | *(none)* | *(none)* | MIT | Environment variables |
| Cereal | [Cereal](https://uscilab.github.io/cereal/) | >= 1.3.2 | BSD | C++ serialization library with multiple formats |
| CSV | [Apache Arrow](https://arrow.apache.org/) | >= 21.0.0 | Apache 2.0 | Tabular textual format |
| flexbuffers | [flatbuffers](https://github.com/google/flatbuffers) | >= 23.5.26 | Apache 2.0 | Schema-less version of flatbuffers, binary format |
| msgpack | [msgpack-c](https://github.com/msgpack/msgpack-c) | >= 6.0.0 | BSL 1.0 | JSON-like binary format |
Expand Down Expand Up @@ -666,11 +669,29 @@ Finally, it is very easy to extend full support to your own classes, refer to th

## Installation

The following compilers are supported:
The following compilers are supported for C++-20:
- GCC 11.4 or higher
- Clang 14.0 or higher
- MSVC 17.8 (19.38) or higher

The following compilers are supported for C++-26:
- GCC 16.2 or higher

### Compiling with C++-26 reflection

To compile reflect-cpp using the standard C++ reflection facilities, pass the CMake option
`REFLECTCPP_USE_CPP26_REFLECTION` together with the compiler flag that activates reflection
support in your compiler (`-freflection` for GCC, `-freflection-latest` for Clang):

```bash
cmake -S . -B build -DCMAKE_CXX_STANDARD=26 -DCMAKE_BUILD_TYPE=Release -DREFLECTCPP_USE_CPP26_REFLECTION=ON -DCMAKE_CXX_FLAGS="-freflection"
cmake --build build -j 4
```

With C++-26 reflection, fixed-size C arrays and inheritance are supported out of the box (no
`-DREFLECT_CPP_C_ARRAYS_OR_INHERITANCE` flag needed), and there are no range restrictions for
enums. Refer to the [documentation](https://rfl.getml.com/cpp26_reflection) for details.

### Using vcpkg

https://vcpkg.io/en/package/reflectcpp
Expand Down
11 changes: 11 additions & 0 deletions docs/c_arrays_and_inheritance.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ inheritance altogether.
Note that C arrays are not the same thing as `std::array`. `std::array` is always
supported and is the recommended alternative.

!!! note "C++-26 reflection"

If you compile reflect-cpp with C++-26 reflection (see [C++26 reflection](cpp26_reflection.md)),
then none of the restrictions in this section apply: fixed-size C arrays and inheritance are
supported out of the box, no flag is required, and the fields may be spread out over
multiple structs.

If you want support for these, you will have to pass the flag `-D REFLECT_CPP_C_ARRAYS_OR_INHERITANCE`
during compilation.

Expand Down Expand Up @@ -85,6 +92,10 @@ struct Derived : Base {
};
```

Note that this restriction does not apply when compiling with C++-26 reflection: with C++-26,
the fields of the base class and the fields of the derived class are combined automatically,
so the example above works as well.

The recommended alternative is to simply use `rfl::Flatten`, which
has no such limitation:

Expand Down
126 changes: 126 additions & 0 deletions docs/cpp26_reflection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# C++26 reflection

reflect-cpp can be compiled in two modes, which use two different reflection implementations:

* **C++-20/23 mode (the default):** reflection is implemented using metaprogramming
techniques (structured bindings, aggregate-initialization analysis, and
[enchantum](https://github.com/ZXShady/enchantum)). This works on a wide range of
compilers, but it has some limitations and comes with a higher compile-time cost.
* **C++-26 mode:** reflection uses the standard C++ reflection facilities
(`<meta>`, [P2996](https://wg21.link/P2996)). This is more powerful, because members
and enumerators are queried directly from the compiler instead of being inferred.

C++-26 mode is opt-in. It requires a compiler that supports the C++ reflection proposal
and a compiler flag to activate it. The rest of this documentation applies to both modes,
except where explicitly noted otherwise.

## Compiling with C++-26 reflection

The following compilers are supported for C++-26:

* GCC 16.2 or higher
* Clang: experimental, only available in Clang builds that implement
[P2996](https://wg21.link/P2996) (such as Bloomberg's
[clang-p2996](https://github.com/bloomberg/clang-p2996) fork)

To compile reflect-cpp with C++-26 reflection, pass the CMake option
`REFLECTCPP_USE_CPP26_REFLECTION` and the compiler flag that activates reflection
support in your compiler:

* GCC: `-freflection`
* Clang: `-freflection-latest`

For example, using cmake:

```bash
cmake -S . -B build -DCMAKE_CXX_STANDARD=26 -DCMAKE_BUILD_TYPE=Release -DREFLECTCPP_USE_CPP26_REFLECTION=ON -DCMAKE_CXX_FLAGS="-freflection"
cmake --build build -j 4
```

The individual parts:

* `-DREFLECTCPP_USE_CPP26_REFLECTION=ON` switches reflect-cpp to the C++-26 reflection
implementation and defines the macro `REFLECTCPP_USE_CPP26_REFLECTION`. If
`CMAKE_CXX_STANDARD` is not set, it will be set to 26 automatically.
* `-DCMAKE_CXX_STANDARD=26` sets the C++ standard to 26.
* `-DCMAKE_CXX_FLAGS="-freflection"` activates the C++ reflection facilities in the
compiler itself. Use `-freflection-latest` on Clang instead.

When compiling with C++-26 reflection, the option `REFLECTCPP_USE_STD_EXPECTED` is
enabled by default as well, which means that `rfl::Result<T>` is an alias for
`std::expected<T, rfl::Error>`. If you want to keep using the built-in `rfl::Result`
type, pass `-DREFLECTCPP_USE_STD_EXPECTED=OFF`.

If you include the source files directly into your own build (see
[Installation](install.md#option-4-include-source-files-into-your-own-build)), add the
compile definition `-DREFLECTCPP_USE_CPP26_REFLECTION` and the appropriate compiler flag
to all translation units that include reflect-cpp.

## What C++-26 reflection changes

### C arrays and inheritance are supported out of the box

In C++-20/23 mode, fixed-size C arrays and inheritance are only supported when you pass
the flag `-DREFLECT_CPP_C_ARRAYS_OR_INHERITANCE`, and inheritance only works when all of
the fields are inside the same struct. Refer to
[C arrays and inheritance](c_arrays_and_inheritance.md) for details.

In C++-26 mode, neither restriction applies:

* Fixed-size C arrays are regular fields as far as the compiler's reflection is concerned,
so they work without any flag.
* The fields of base classes are combined with the fields of the derived class, so the
fields may be spread out over multiple structs.

For example, this is not supported in C++-20/23 mode, but works as-is in C++-26 mode:

```cpp
struct Base {
int x;
};

struct Derived : Base {
int y;
};

const auto derived = Derived{1, 2};

rfl::json::write(derived);
```

This results in the following JSON string:

```json
{"x":1,"y":2}
```

### No range restrictions for enums

In C++-20/23 mode, enum values must be in the range `[RFL_ENUM_RANGE_MIN,
RFL_ENUM_RANGE_MAX]`, where the default range is `[-256, 256]`. Refer to
[Enums](enums.md) for details.

In C++-26 mode, the enumerators are read directly from the compiler, so there is no
restriction on the range of enum values, and `RFL_ENUM_RANGE_MIN` and `RFL_ENUM_RANGE_MAX`
are not needed.

### Lower compile-time cost

In C++-20/23 mode, reflect-cpp infers the fields of a struct by figuring out how the
struct can be constructed. This requires a lot of compile-time work, especially in the
presence of C arrays and inheritance. In C++-26 mode, the fields and enumerators are
queried directly from the compiler's reflection information, which is considerably
cheaper.

## Summary

| Feature | C++-20/23 | C++-26 |
|---------|-----------|--------|
| Supported compilers | GCC 11.4+, Clang 14.0+, MSVC 17.8+ | GCC 16.2+ (Clang: experimental) |
| Compiler flag | *(none)* | `-freflection` (GCC), `-freflection-latest` (Clang) |
| CMake option | *(none)* | `-DREFLECTCPP_USE_CPP26_REFLECTION=ON` |
| Fixed-size C arrays | requires `-DREFLECT_CPP_C_ARRAYS_OR_INHERITANCE` | supported out of the box |
| Inheritance | requires `-DREFLECT_CPP_C_ARRAYS_OR_INHERITANCE`, fields must be in a single struct | supported out of the box, fields may be spread over multiple structs |
| Enum value range | restricted to `[RFL_ENUM_RANGE_MIN, RFL_ENUM_RANGE_MAX]` (default `[-256, 256]`) | no restriction |
| `rfl::Result` | built-in type (or `std::expected` via `-DREFLECTCPP_USE_STD_EXPECTED`) | `std::expected` by default |
| Compile time | higher | lower |
7 changes: 7 additions & 0 deletions docs/enums.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ However, some limitations apply:
2. Enum values must be in the range `[RFL_ENUM_RANGE_MIN, RFL_ENUM_RANGE_MAX]`. If the range is not specified, the
default range is `[-256, 256]`.

!!! note "C++-26 reflection"

This restriction only applies when compiling reflect-cpp with C++-20 or C++-23. When
compiling with [C++-26 reflection](cpp26_reflection.md), the enumerators are read
directly from the compiler, so there is no restriction on the range of enum values,
and `RFL_ENUM_RANGE_MIN` and `RFL_ENUM_RANGE_MAX` are not needed.

- You can specify a custom range for the all enum values by defining `RFL_ENUM_RANGE_MIN` and `RFL_ENUM_RANGE_MAX`
before including the reflect-cpp header:

Expand Down
33 changes: 32 additions & 1 deletion docs/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,44 @@ hide:

# Installation

The following compilers are supported:
The following compilers are supported for C++-20:
- GCC 11.4 or higher
- Clang 14.0 or higher
- MSVC 17.8 (19.38) or higher

The following compilers are supported for C++-26:
- GCC 16.2 or higher

You can include the source files into your build or compile it using cmake and vcpkg.

## Compiling with C++-26 reflection

By default, reflect-cpp uses a C++-20 compatible reflection implementation, which works
across a wide range of compilers. If you want to use the standard C++ reflection
facilities (`<meta>`, [P2996](https://wg21.link/P2996)), you can compile reflect-cpp in
C++-26 mode instead. This is more powerful: fixed-size C arrays and inheritance are
supported out of the box, and there are no range restrictions for enums. Refer to
[C++26 reflection](cpp26_reflection.md) for details.

To enable C++-26 reflection, pass the CMake option `REFLECTCPP_USE_CPP26_REFLECTION` and
the compiler flag that activates reflection support in your compiler:

* GCC: `-freflection`
* Clang: `-freflection-latest` (experimental, only available in Clang builds that
implement [P2996](https://wg21.link/P2996), such as Bloomberg's
[clang-p2996](https://github.com/bloomberg/clang-p2996) fork)

For example, using cmake:

```bash
cmake -S . -B build -DCMAKE_CXX_STANDARD=26 -DCMAKE_BUILD_TYPE=Release -DREFLECTCPP_USE_CPP26_REFLECTION=ON -DCMAKE_CXX_FLAGS="-freflection"
cmake --build build -j 4
```

If you include the source files into your own build (see Option 4 below), also add the
compile definition `-DREFLECTCPP_USE_CPP26_REFLECTION` and the appropriate compiler flag
to all translation units that include reflect-cpp.

## Option 1: Using vcpkg

Refer to [this port](https://vcpkg.link/ports/reflectcpp):
Expand Down
1 change: 1 addition & 0 deletions include/rfl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
#include "rfl/make_named_tuple.hpp"
#include "rfl/name_t.hpp"
#include "rfl/named_tuple_t.hpp"
#include "rfl/num_fields.hpp"
#include "rfl/parsing/CustomParser.hpp"
#include "rfl/patterns.hpp"
#include "rfl/remove_fields.hpp"
Expand Down
8 changes: 4 additions & 4 deletions include/rfl/Literal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@ class Literal {
/// Copy constructor - constructs a Literal from another literal with the same
/// fields.
/// @param _other The literal to copy from
Literal(const Literal<fields_...>& _other) = default;
constexpr Literal(const Literal<fields_...>& _other) = default;

/// Move constructor - constructs a Literal from another literal with the same
/// fields.
/// @param _other The literal to move from
Literal(Literal<fields_...>&& _other) noexcept = default;
constexpr Literal(Literal<fields_...>&& _other) noexcept = default;

/// Constructs a Literal from a string value.
/// @param _str The string representing one of the literal's allowed values
Expand All @@ -61,7 +61,7 @@ class Literal {
Literal(const std::string& _str) : value_(find_value(_str).value()) {}

/// Default constructor - initializes to the first value (index 0).
Literal() : value_(0) {}
constexpr Literal() : value_(0) {}

/// Destructor.
~Literal() = default;
Expand All @@ -87,7 +87,7 @@ class Literal {
/// @tparam _value The index of the value (must be less than num_fields_)
/// @return A Literal representing the value at the specified index
template <ValueType _value>
static Literal<fields_...> from_value() {
static constexpr Literal<fields_...> from_value() {
static_assert(_value < num_fields_,
"Value cannot exceed number of fields.");
return Literal<fields_...>(_value);
Expand Down
Loading
Loading