mirror of
https://github.com/boostorg/pfr.git
synced 2026-01-19 04:22:13 +00:00
645 lines
25 KiB
Plaintext
645 lines
25 KiB
Plaintext
[library Boost.PFR
|
|
[quickbook 1.6]
|
|
[version 2.2]
|
|
[copyright 2016-2025 Antony Polukhin]
|
|
[category Language Features Emulation]
|
|
[license
|
|
Distributed under the Boost Software License, Version 1.0.
|
|
(See accompanying file LICENSE_1_0.txt or copy at
|
|
[@http://www.boost.org/LICENSE_1_0.txt])
|
|
]
|
|
]
|
|
|
|
[section Intro]
|
|
|
|
Boost.PFR is a C++14 library for a very basic reflection. It gives you access to structure elements by index and provides other `std::tuple` like methods for user defined types without macro or boilerplate code:
|
|
|
|
[import ../example/motivating_example0.cpp]
|
|
[pfr_motivating_example]
|
|
|
|
Experiment with the sample [@https://godbolt.org/z/PfYsWKb7v online].
|
|
See [link boost_pfr.limitations_and_configuration [*limitations]].
|
|
|
|
|
|
[h2 Usecase example]
|
|
|
|
Imagine that you are writing the wrapper library for a database. Depending on the usage of Boost.PFR users code will look differently:
|
|
|
|
[table:hand_made_vs_pfr_1
|
|
[[ Without Boost.PFR ] [ With Boost.PFR ]]
|
|
[[
|
|
```
|
|
#include <db/api.hpp>
|
|
|
|
struct user_info {
|
|
std::int64_t id;
|
|
std::string name, email, login;
|
|
};
|
|
|
|
user_info retrieve_friend(std::string_view name) {
|
|
std::tuple info_tuple
|
|
= db::one_row_as<std::int64_t, std::string, std::string, std::string>(
|
|
"SELECT id, name, email, login FROM user_infos WHERE name=$0",
|
|
name
|
|
);
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
user_info info {
|
|
std::move(std::get<0>(info_tuple)),
|
|
std::move(std::get<1>(info_tuple)),
|
|
std::move(std::get<2>(info_tuple)),
|
|
std::move(std::get<3>(info_tuple)),
|
|
}
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
auto friend_info = ask_user_for_friend(std::move(info));
|
|
|
|
db::insert(
|
|
"INSERT INTO user_infos(id, name, email, login) VALUES ($0, $1, $2, $3)",
|
|
friend_info.id, //////////////////////////////////////////////////////
|
|
friend_info.name, // Users are forced to enumerate fields because your
|
|
friend_info.email, // library can not iterate over the fields of a user
|
|
friend_info.login // provided structure
|
|
);
|
|
|
|
return friend_info;
|
|
}
|
|
```
|
|
][
|
|
```
|
|
#include <db/api.hpp>
|
|
|
|
struct user_info {
|
|
std::int64_t id;
|
|
std::string name, email, login;
|
|
};
|
|
|
|
user_info retrieve_friend(std::string_view name) {
|
|
// With Boost.PFR you can put data directly into user provided structures
|
|
user_info info = db::one_row_as<user_info>(
|
|
"SELECT id, name, email, login FROM user_infos WHERE name=$0",
|
|
name
|
|
);
|
|
|
|
////////////////// No boilerplate code to move data around //////////////////
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
auto friend_info = ask_user_for_friend(std::move(info));
|
|
|
|
db::insert(
|
|
"INSERT INTO user_infos(id, name, email, login) VALUES ($0, $1, $2, $3)",
|
|
friend_info /////////////////////////////////////////////////////////
|
|
// Boost.PFR allows you to iterate over all the fields
|
|
// of a user provided structure
|
|
//
|
|
);
|
|
|
|
return friend_info;
|
|
}
|
|
```
|
|
]]
|
|
]
|
|
|
|
Otherwise your library could require a customization point for a user type:
|
|
|
|
|
|
[table:hand_made_vs_pfr_2
|
|
[[ Without Boost.PFR ] [ With Boost.PFR ]]
|
|
[[
|
|
```
|
|
#include <db/api.hpp>
|
|
|
|
struct user_info {
|
|
std::int64_t id;
|
|
std::string name, email, login;
|
|
};
|
|
|
|
/// Customizations via hand-written code ////////////////////////////////////////
|
|
auto db_api_tie(user_info& ui) noexcept {
|
|
return std::tie(ui.id, ui.name, ui.email, ui.login);
|
|
}
|
|
|
|
auto db_api_tie(const user_info& ui) noexcept {
|
|
return std::tie(ui.id, ui.name, ui.email, ui.login);
|
|
}
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
```
|
|
][
|
|
```
|
|
#include <db/api.hpp>
|
|
|
|
struct user_info {
|
|
std::int64_t id;
|
|
std::string name, email, login;
|
|
};
|
|
|
|
//////// With Boost.PFR there's no need in hand written customizations //////////
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
```
|
|
]]
|
|
]
|
|
|
|
|
|
Imagine that you are writing a serialization library. Serialization of user
|
|
provided structures (and nested structures) with Boost.PFR it is just as simple as:
|
|
|
|
```
|
|
void Write(Writer& writer, int value);
|
|
void Write(Writer& writer, std::string_view value);
|
|
|
|
template <typename T>
|
|
std::enable_if_t<boost::pfr::is_implicitly_reflectable_v<T>> Write(Writer& writer, const T& value) {
|
|
boost::pfr::for_each_field(
|
|
value, [&writer](const auto& field) { Write(writer, field); });
|
|
}
|
|
```
|
|
|
|
With Boost.PFR the code is shorter, more readable and more pleasant to write.
|
|
|
|
[note All the above examples were inspired by the Boost.PFR usage in [@https://github.com/userver-framework/userver 🐙 userver framework].]
|
|
|
|
|
|
[h2 Out of the box functionality ]
|
|
|
|
Boost.PFR adds the following out-of-the-box functionality for aggregate initializable structures:
|
|
|
|
* comparison functions
|
|
* heterogeneous comparators
|
|
* hash
|
|
* IO streaming
|
|
* access to members by index or type
|
|
* access to member's names by index
|
|
* member type retrieval
|
|
* methods for cooperation with `std::tuple` for members
|
|
* methods for cooperation with `std::array` for member's names
|
|
* methods to visit each field of the structure
|
|
* trait to detect potential ability to reflect type, and ability to override trait's decision in user-side code
|
|
|
|
Boost.PFR is a header only library that does not depend on Boost. You can just copy the content of the "include" folder [@https://github.com/boostorg/pfr from the Boost.PFR github] into your project, and the library will work fine. For a version of the library without `boost::` namespace see [@https://github.com/apolukhin/pfr_non_boost PFR].
|
|
|
|
[caution Recommended C++ Standards are C++20 and above. C++17 completely enough for a user who doesn't want accessing name of structure member. Library requires at least C++14! Pre C++14 compilers (C++11, C++03...) are not supported]
|
|
|
|
[endsect]
|
|
|
|
|
|
[section Short Examples for the Impatient]
|
|
|
|
[import ../example/quick_examples.cpp]
|
|
|
|
|
|
[table:quick_examples
|
|
[[ Code snippet ] [ Reference: ]]
|
|
[
|
|
[ [pfr_quick_examples_get] ]
|
|
[ [funcref boost::pfr::get] ]
|
|
][
|
|
[ [pfr_quick_examples_get_name] ]
|
|
[ [funcref boost::pfr::get_name] ]
|
|
][
|
|
[ [pfr_quick_examples_ops] ]
|
|
[
|
|
|
|
[headerref boost/pfr/ops.hpp Header boost/pfr/ops.hpp]:
|
|
|
|
* [funcref boost::pfr::eq]
|
|
|
|
* [funcref boost::pfr::ne]
|
|
|
|
* [funcref boost::pfr::gt]
|
|
|
|
* ...
|
|
|
|
]
|
|
][
|
|
[ [pfr_quick_examples_for_each] ]
|
|
[
|
|
[funcref boost::pfr::for_each_field]
|
|
|
|
[funcref boost::pfr::io]
|
|
]
|
|
][
|
|
[ [pfr_quick_examples_for_each_with_name] ]
|
|
[ [funcref boost::pfr::for_each_field_with_name] ]
|
|
][
|
|
[ [pfr_quick_examples_functions_for] ]
|
|
[ [macroref BOOST_PFR_FUNCTIONS_FOR] ]
|
|
][
|
|
[ [pfr_quick_examples_eq_fields] ]
|
|
[
|
|
[headerref boost/pfr/ops_fields.hpp Header boost/pfr/ops_fields.hpp ]:
|
|
|
|
* [funcref boost::pfr::eq_fields]
|
|
|
|
* [funcref boost::pfr::ne_fields]
|
|
|
|
* [funcref boost::pfr::gt_fields]
|
|
|
|
* ...
|
|
|
|
[headerref boost/pfr/io_fields.hpp Header boost/pfr/io_fields.hpp ]
|
|
|
|
* [funcref boost::pfr::io_fields]
|
|
|
|
]
|
|
][
|
|
[ [pfr_quick_examples_for_each_idx] ]
|
|
[ [funcref boost::pfr::for_each_field] ]
|
|
][
|
|
[ [pfr_quick_examples_tuple_size] ]
|
|
[ [classref boost::pfr::tuple_size] ]
|
|
][
|
|
[ [pfr_quick_examples_structure_to_tuple] ]
|
|
[ [funcref boost::pfr::structure_to_tuple] ]
|
|
][
|
|
[ [pfr_quick_examples_structure_tie] ]
|
|
[ [funcref boost::pfr::structure_tie] ]
|
|
]]
|
|
|
|
|
|
|
|
[endsect]
|
|
|
|
|
|
[section Tutorial]
|
|
|
|
[import ../example/sample_printing.cpp]
|
|
[import ../example/get.cpp]
|
|
[import ../example/get_name.cpp]
|
|
|
|
|
|
[section Why tuples are bad and aggregates are more preferable?]
|
|
|
|
`std::tuple` and `std::pair` are good for generic programming, however they have disadvantages. First of all, code that uses them becomes barely readable. Consider two definitions:
|
|
|
|
[table:tuples_vs_aggregates
|
|
[[ Tuple ] [ Aggregate ]]
|
|
[[
|
|
```
|
|
using auth_info_tuple = std::tuple<
|
|
std::int64_t, // What does this integer represents?
|
|
std::int64_t,
|
|
std::time_t
|
|
>;
|
|
```
|
|
][
|
|
```
|
|
struct auth_info_aggregate {
|
|
std::int64_t user_id; // Oh, now I see!
|
|
std::int64_t session_id;
|
|
std::time_t valid_till;
|
|
};
|
|
```
|
|
]]
|
|
]
|
|
|
|
Definition via aggregate initializable structure is much more clear. Same story with usages: `return std::get<1>(value);` vs. `return value.session_id;`.
|
|
|
|
Another advantage of aggregates is a more efficient copy, move construction and assignments.
|
|
|
|
Because of the above issues some guidelines recommend to [*use aggregates instead of tuples]. However aggregates fail when it comes to the functional like programming.
|
|
|
|
Boost.PFR library [*provides tuple like methods for aggregate initializable structures], making aggregates usable in contexts where only tuples were useful.
|
|
|
|
[endsect]
|
|
|
|
[section Accessing structure member by index] [pfr_example_get] [endsect]
|
|
[section Custom printing of aggregates] [pfr_sample_printing] [endsect]
|
|
|
|
|
|
[section Three ways of getting operators ]
|
|
|
|
There are three ways to start using Boost.PFR hashing, comparison and streaming for type `T` in your code. Each method has its own drawbacks and suits own cases.
|
|
|
|
[table:ops_comp Different approaches for operators
|
|
[[ Approach
|
|
][ When to use
|
|
][ Operators could be found by ADL ][ Works for local types ][ Usable locally, without affecting code from other scopes ][ Ignores implicit conversion operators ][ Respects user defined operators ]]
|
|
|
|
[[
|
|
[headerref boost/pfr/ops.hpp boost/pfr/ops.hpp: eq, ne, gt, lt, le, ge]
|
|
|
|
[headerref boost/pfr/io.hpp boost/pfr/io.hpp: io]
|
|
][
|
|
Use when you need to compare values by provided for them operators or via field-by-field comparison.
|
|
][ no ][ yes ][ yes ][ no ][ yes ]]
|
|
|
|
[[
|
|
[macroref BOOST_PFR_FUNCTIONS_FOR BOOST_PFR_FUNCTIONS_FOR(T)]
|
|
][
|
|
Use near the type definition to define the whole set of operators for your type.
|
|
][ yes ][ no ][ no ][ yes for T ] [ no (compile time error) ]]
|
|
|
|
[[
|
|
[headerref boost/pfr/ops_fields.hpp boost/pfr/ops_fields.hpp: eq_fields, ne_fields, gt_fields, lt_fields, le_fields, ge_fields]
|
|
|
|
[headerref boost/pfr/io.hpp boost/pfr/io_fields.hpp: io_fields]
|
|
][
|
|
Use to implement the required set of operators for your type.
|
|
][ no ][ yes ][ yes ][ yes ][ yes ]]
|
|
]
|
|
|
|
More detailed description follows:
|
|
|
|
[*1. `eq, ne, gt, lt, le, ge, io` approach]
|
|
|
|
This method is good if you're writing generic algorithms and need to use operators from Boost.PFR only if there are no operators defined for the type:
|
|
|
|
```
|
|
#include <boost/pfr/ops.hpp>
|
|
|
|
template <class T>
|
|
struct uniform_comparator_less {
|
|
bool operator()(const T& lhs, const T& rhs) const noexcept {
|
|
// If T has operator< or conversion operator then it is used.
|
|
return boost::pfr::lt(lhs, rhs);
|
|
}
|
|
};
|
|
```
|
|
This methods effects are local to the function. It works even for local types, like structures defined in functions.
|
|
|
|
|
|
[*2. BOOST_PFR_FUNCTIONS_FOR(T) approach]
|
|
|
|
This method is good if you're writing a structure and wish to define operators for that structure.
|
|
```
|
|
#include <boost/pfr/functions_for.hpp>
|
|
|
|
struct pair_like {
|
|
int first;
|
|
short second;
|
|
};
|
|
|
|
BOOST_PFR_FUNCTIONS_FOR(pair_like) // Defines operators
|
|
|
|
// ...
|
|
|
|
assert(pair_like{1, 2} < pair_like{1, 3});
|
|
```
|
|
Argument Dependant Lookup works well. `std::less` will find the operators for `struct pair_like`. [macroref BOOST_PFR_FUNCTIONS_FOR BOOST_PFR_FUNCTIONS_FOR(T)]
|
|
can not be used for local types. It does not respect conversion operators of `T`, so for example the following code
|
|
will output different values:
|
|
```
|
|
#include <boost/pfr/functions_for.hpp>
|
|
|
|
struct empty {
|
|
operator std::string() { return "empty{}"; }
|
|
};
|
|
// Uncomment to get different output:
|
|
// BOOST_PFR_FUNCTIONS_FOR(empty)
|
|
|
|
// ...
|
|
std::cout << empty{}; // Outputs `empty{}` if BOOST_PFR_FUNCTIONS_FOR(empty) is commented out, '{}' otherwise.
|
|
```
|
|
|
|
[*3. `eq_fields, ne_fields, gt_fields, lt_fields, le_fields, ge_fields, io_fields` approach]
|
|
|
|
This method is good if you're willing to provide only some operators for your type:
|
|
|
|
```
|
|
#include <boost/pfr/io_fields.hpp>
|
|
|
|
struct pair_like {
|
|
int first;
|
|
std::string second;
|
|
};
|
|
|
|
inline std::ostream& operator<<(std::ostream& os, const pair_like& x) {
|
|
return os << bost::pfr::io_fields(x);
|
|
}
|
|
```
|
|
|
|
All the `*_fields` functions do ignore user defined operators and work only with fields of a type. This makes them perfect for defining you own operators.
|
|
|
|
[endsect]
|
|
|
|
|
|
[section Reflection of unions ]
|
|
|
|
You could use tuple-like representation if a type contains union. But be sure that operations for union are manually defined:
|
|
|
|
```
|
|
#include <boost/pfr/ops.hpp>
|
|
|
|
union test_union {
|
|
int i;
|
|
float f;
|
|
};
|
|
|
|
inline bool operator==(test_union l, test_union r) noexcept; // Compile time error without this operator
|
|
|
|
bool some_function(test_union f1, test_union f2) {
|
|
return boost::pfr::eq(f1, f2); // OK
|
|
}
|
|
|
|
```
|
|
|
|
Reflection of unions is disabled in the Boost.PFR library for safety reasons. Alas, there's no way to find out [*active] member of a union and accessing an inactive member is an Undefined Behavior. For example, library could always return the first member, but ostreaming `u` in `union {char* c; long long ll; } u; u.ll= 1;` will crash your program with an invalid pointer dereference.
|
|
|
|
Any attempt to reflect unions leads to a compile time error. In many cases a static assert is triggered that outputs the following message:
|
|
|
|
```
|
|
error: static_assert failed "====================> Boost.PFR: For safety reasons it is forbidden
|
|
to reflect unions. See `Reflection of unions` section in the docs for more info."
|
|
```
|
|
|
|
[endsect]
|
|
|
|
|
|
[section Reflection of field name ]
|
|
|
|
[pfr_example_get_name]
|
|
|
|
See [link boost_pfr.limitations_and_configuration [*Limitations and Configuration]].
|
|
|
|
[endsect]
|
|
|
|
[endsect]
|
|
|
|
|
|
[section Limitations and Configuration]
|
|
|
|
[caution Recommended C++ Standards are C++20 and above. C++17 completely enough for a user who doesn't want accessing name of structure member. Library requires at least C++14! Pre C++14 compilers (C++11, C++03...) are not supported. ]
|
|
|
|
Boost.PFR library works with types that satisfy the requirements of `SimpleAggregate`: aggregate types without base classes, `const` fields, references, or C arrays:
|
|
|
|
```
|
|
struct simple_aggregate { // SimpleAggregate
|
|
std::string name;
|
|
int age;
|
|
boost::uuids::uuid uuid;
|
|
};
|
|
|
|
struct empty { // SimpleAggregate
|
|
};
|
|
|
|
struct aggregate : empty { // not a SimpleAggregate
|
|
std::string name;
|
|
int age;
|
|
boost::uuids::uuid uuid;
|
|
};
|
|
```
|
|
The library may work with aggregates that don't satisfy the requirements of `SimpleAggregate`, but the behavior tends to be non-portable.
|
|
|
|
Boost.PFRs extraction of field name works with only `SimpleAggregate` types.
|
|
|
|
|
|
[h2 Configuration Macro]
|
|
|
|
By default Boost.PFR [*auto-detects your compiler abilities] and automatically defines the configuration macro into appropriate values. If you wish to override that behavior, just define:
|
|
[table:linkmacro Macros
|
|
[[Macro name] [Effect]]
|
|
[[*BOOST_PFR_USE_CPP17*] [Define to `1` if you wish to override Boost.PFR choice and use C++17 structured bindings for reflection. Define to `0` to override Boost.PFR choice and disable C++17 structured bindings usage.]]
|
|
[[*BOOST_PFR_USE_LOOPHOLE*] [Define to `1` if you wish to override Boost.PFR choice and exploit [@http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#2118 CWG 2118] for reflection. Define to `0` to override Boost.PFR choice and disable CWG 2118 usage.]]
|
|
[[*BOOST_PFR_USE_STD_MAKE_INTEGRAL_SEQUENCE*] [Define to `0` if you are hit by the template instantiation depth issues with `std::make_integer_sequence` and wish to use Boost.PFR version of that metafunction. Define to `1` to override Boost.PFR detection logic. ]]
|
|
[[*BOOST_PFR_HAS_GUARANTEED_COPY_ELISION*] [Define to `0` if your compiler does not implement C++17 guaranteed copy elision properly and fails to reflect aggregates with non-movable fields. Define to `1` to override Boost.PFR detection logic. ]]
|
|
[[*BOOST_PFR_ENABLE_IMPLICIT_REFLECTION*] [Define to `0` if you are hit by lots of non-effective choices made by implicitly reflection. Define to `1` to override Boost.PFR detection logic. ]]
|
|
[[*BOOST_PFR_CORE_NAME_ENABLED*] [On platforms where field name extraction is not supported, the 'boost/pfr/config.hpp' header defines the BOOST_PFR_CORE_NAME_ENABLED macro equal to 0. Defining this macro as 0 before including the header disables the ability to get a field name. ]]
|
|
[[*BOOST_PFR_FUNCTION_SIGNATURE*] [For known compilers defined to a compiler specific macro, that outputs the whole function signature including non-type template parameters. ]]
|
|
[[*BOOST_PFR_CORE_NAME_PARSING*] [Describes extraction of field name from BOOST_PFR_FUNCTION_SIGNATURE macro. See details below. ]]
|
|
[[*BOOST_PFR_ENABLED*] [On platforms where Boost.PFR is not supported, the `boost/pfr/config.hpp` header defines the BOOST_PFR_ENABLED macro equal to 0. Defining this macro as 0 before including the header disables the Boost.PFR library. ]]
|
|
]
|
|
|
|
|
|
[h2 Details on Limitations]
|
|
|
|
The Boost.PFRs reflection has some limitations that depend on a C++ Standard and compiler capabilities:
|
|
|
|
* Static variables are ignored
|
|
* T must be aggregate initializable without base classes
|
|
* if T contains C arrays then the result of reflection may differ depending on the C++ version and library configuration
|
|
* Additional limitations if [*BOOST_PFR_USE_CPP17 == 0]:
|
|
* Non of the member fields should have a template constructor from one parameter.
|
|
* Additional limitations if [*BOOST_PFR_USE_LOOPHOLE == 0]:
|
|
* T must be constexpr aggregate initializable and all its fields must be constexpr default constructible
|
|
* [funcref boost::pfr::get], [funcref boost::pfr::structure_to_tuple], [funcref boost::pfr::structure_tie], [headerref boost/pfr/core.hpp boost::pfr::tuple_element] require T to be a POD type with built-in types only.
|
|
|
|
The Boost.PFRs extraction of field name has some limitations that depend on a C++ Standard and compiler capabilities:
|
|
|
|
* T should be usable like `extern T t;`, i.e. has a non-internal linkage.
|
|
|
|
[h2 Adjusting BOOST_PFR_CORE_NAME_PARSING]
|
|
|
|
`BOOST_PFR_CORE_NAME_PARSING` is already set up for most of the popular compilers. You need to adjust it only
|
|
if some static_assert in the library complained on `BOOST_PFR_CORE_NAME_PARSING`.
|
|
|
|
To do that:
|
|
|
|
# Build `test/core_name/print_name.cpp` with your compiler and run it
|
|
# Define BOOST_PFR_CORE_NAME_PARSING to `(skip_at_begin, skip_at_end, "")`, where
|
|
* `skip_at_begin` is equal to characters count before the first occurrence of `user_defined_field` in output
|
|
* `skip_at_end` is equal to characters count after last occurrence of `user_defined_field` in output
|
|
# Check that `test/core_name/print_name.cpp` returns "user_defined_field"
|
|
# If it does not return `user_defined_field`, then define BOOST_PFR_CORE_NAME_PARSING to `(skip_at_begin, skip_at_end, "T = ")`, where
|
|
* `skip_at_begin` is equal to `skip_at_begin` at step 2
|
|
* `skip_at_end` is equal to `skip_at_end` at step 2
|
|
* `"T = "` is equal to characters that are right before the `user_defined_field` in output, use `backward("T = ")` to search for the occurange in the string from the right
|
|
# (optional, but highly recommended) [@https://github.com/boostorg/pfr/issues create ticket] with
|
|
feature request to add your compiler to supported compilers list. Include
|
|
parameters provided to `BOOST_PFR_CORE_NAME_PARSING` macro [*and] the initial output of `test/core_name/print_name.cpp`.
|
|
|
|
[endsect]
|
|
|
|
[section PFR as a C++20 module]
|
|
|
|
[caution C++20 PFR module support is on early stage, targets, flags and behavior may change in the future]
|
|
|
|
If using modern CMake define CMake option `-DBOOST_USE_MODULES=1` to build a C++20 module and
|
|
make the `Boost::pfr` CMake target provide it. After that an explicit usage of C++20 module `boost.pfr` is allowed:
|
|
|
|
[import ../modules/usage_sample.cpp]
|
|
[pfr_module_example]
|
|
|
|
The `Boost::pfr` CMake target gives an ability to mix includes and imports of the PFR library in different translation units. Moreover,
|
|
if `BOOST_USE_MODULES` macro is defined then all the `boost/pfr/...` includes implicilty do `import boost.pfr;` to give all the
|
|
benifits of modules without changing the existing code.
|
|
|
|
[note For better compile times make sure that `import std;` is available when building the `boost.pfr` module (in CMake logs there should be
|
|
a 'Using `import std;`' message). ]
|
|
|
|
If not using CMake, then the module could be build manually from the `module/pfr.cppm` file.
|
|
|
|
For manual module build the following commands could be used for clang compiler:
|
|
|
|
```
|
|
cd pfr/module
|
|
clang++ -I ../include -std=c++20 --precompile -x c++-module pfr.cppm
|
|
```
|
|
|
|
After that, the module could be used in the following way:
|
|
|
|
```
|
|
clang++ -std=c++20 -fmodule-file=pfr.pcm pfr.pcm usage_sample.cpp
|
|
```
|
|
|
|
[endsect]
|
|
|
|
[section How it works]
|
|
|
|
[h2 Fields count detection and getting references to members]
|
|
|
|
Short description:
|
|
|
|
# at compile-time: use aggregate initialization to detect fields count in user-provided structure
|
|
* [*BOOST_PFR_USE_CPP17 == 1]:
|
|
# at compile-time: structured bindings are used to decompose a type `T` to known amount of fields
|
|
* [*BOOST_PFR_USE_CPP17 == 0 && BOOST_PFR_USE_LOOPHOLE == 1]:
|
|
# at compile-time: use aggregate initialization to detect fields count in user-provided structure
|
|
# at compile-time: make a structure that is convertible to anything and remember types it has been converted to during aggregate initialization of user-provided structure
|
|
# at compile-time: using knowledge from previous steps create a tuple with exactly the same layout as in user-provided structure
|
|
# at compile-time: find offsets for each field in user-provided structure using the tuple from previous step
|
|
# at run-time: get pointer to each field, knowing the structure address and each field offset
|
|
# at run-time: a tuple of references to fields is returned => all the tuple methods are available for the structure
|
|
* [*BOOST_PFR_USE_CPP17 == 0 && BOOST_PFR_USE_LOOPHOLE == 0]:
|
|
# at compile-time: let `I` be is an index of current field, it equals 0
|
|
# at run-time: `T` is constructed and field `I` is aggregate initialized using a separate instance of structure that is convertible to anything [note Additional care is taken to make sure that all the information about `T` is available to the compiler and that operations on `T` have no side effects, so the compiler can optimize away the unnecessary temporary objects.]
|
|
# at compile-time: `I += 1`
|
|
# at compile-time: if `I` does not equal fields count goto step [~c.] from inside of the conversion operator of the structure that is convertible to anything
|
|
# at compile-time: using knowledge from previous steps create a tuple with exactly the same layout as in user-provided structure
|
|
# at compile-time: find offsets for each field in user-provided structure using the tuple from previous step
|
|
# at run-time: get pointer to each field, knowing the structure address and each field offset
|
|
# at run-time: a tuple of references to fields is returned => all the tuple methods are available for the structure
|
|
|
|
Long description of some basics: [@https://youtu.be/UlNUNxLtBI0 Antony Polukhin: Better C++14 reflections].
|
|
Long description of some basics of C++14 with [link boost_pfr.limitations_and_configuration [*BOOST_PFR_USE_LOOPHOLE == 0]]: [@https://youtu.be/abdeAew3gmQ Antony Polukhin: C++14 Reflections Without Macros, Markup nor External Tooling].
|
|
Description of the [*BOOST_PFR_USE_LOOPHOLE == 1] technique by its inventor Alexandr Poltavsky [@http://alexpolt.github.io/type-loophole.html in his blog].
|
|
|
|
[h2 Field name retrieval]
|
|
|
|
# at compile-time:
|
|
* Get references to members of an object of type `T` in `constexpr` function
|
|
* Feed the reference from previous as a template parameter to a `constexpr` function with `template <auto member_ptr>`.
|
|
That function returns `__PRETTY_FUNCTION__` or some other vendor specific macro that prints the whole name of a function
|
|
along with the template arguments.
|
|
* The returned value from previous step contains the member name ([@https://godbolt.org/z/K4aWdcE9G godbolt example]). Do some
|
|
compiler specific parsing of the value and make a `std::string_view` that contains only the member name.
|
|
# at run-time: return the `std::string_view` with the member name.
|
|
|
|
[endsect]
|
|
|
|
[section Acknowledgements]
|
|
|
|
Many thanks to Bruno Dutra for showing the technique to precisely reflect aggregate initializable type in C++14 [@https://github.com/boostorg/pfr/issues/5 Manual type registering/structured bindings might be unnecessary].
|
|
|
|
Many thanks to Alexandr Poltavsky for initial implementation the [*BOOST_PFR_USE_LOOPHOLE == 1] technique and for describing it [@http://alexpolt.github.io/type-loophole.html in his blog].
|
|
|
|
Many thanks to Chris Beck for implementing the detect-offsets-and-get-field-address functionality that avoids Undefined Behavior of reinterpret_casting layout compatible structures.
|
|
|
|
Many thanks to the Boost people who participated in the formal review, especially to Benedek Thaler, Steven Watanabe and Andrzej Krzemienski.
|
|
|
|
[endsect]
|
|
|
|
[xinclude autodoc_pfr.xml]
|