• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1[library Boost.PFR
2    [quickbook 1.6]
3    [version 2.0]
4    [copyright 2016-2021 Antony Polukhin]
5    [category Language Features Emulation]
6    [license
7        Distributed under the Boost Software License, Version 1.0.
8        (See accompanying file LICENSE_1_0.txt or copy at
9        [@http://www.boost.org/LICENSE_1_0.txt])
10    ]
11]
12
13[section Intro]
14
15Boost.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:
16
17[import ../example/motivating_example0.cpp]
18[pfr_motivating_example]
19
20See [link boost_pfr.limitations_and_configuration [*limitations]].
21
22
23[h2 Usecase example]
24
25Imagine that you are writing the wrapper library for a database. Depending on the usage of Boost.PFR users code will look differently:
26
27[table:hand_made_vs_pfr_1
28[[ Without Boost.PFR ] [ With Boost.PFR ]]
29[[
30```
31#include <db/api.hpp>
32
33struct user_info {
34    std::int64_t id;
35    std::string name, email, login;
36};
37
38user_info retrieve_friend(std::string_view name) {
39    std::tuple info_tuple
40      = db::one_row_as<std::int64_t, std::string, std::string, std::string>(
41        "SELECT id, name, email, login FROM user_infos WHERE name=$0",
42        name
43    );
44
45    ////////////////////////////////////////////////////////////////////////////////
46    user_info info {
47        std::move(std::get<0>(info_tuple)),
48        std::move(std::get<1>(info_tuple)),
49        std::move(std::get<2>(info_tuple)),
50        std::move(std::get<3>(info_tuple)),
51    }
52    ////////////////////////////////////////////////////////////////////////////////
53
54    auto friend_info = ask_user_for_friend(std::move(info));
55
56    db::insert(
57        "INSERT INTO user_infos(id, name, email, login) VALUES ($0, $1, $2, $3)",
58        std::move(friend_info.id),    //////////////////////////////////////////////
59        std::move(friend_info.name),  // Users are forced to move individual fields
60        std::move(friend_info.email), // because your library can not iterate over
61        std::move(friend_info.login)  // the fields of a user provided structure
62    );
63
64    return friend_info;
65}
66```
67][
68```
69#include <db/api.hpp>
70
71struct user_info {
72    std::int64_t id;
73    std::string name, email, login;
74};
75
76user_info retrieve_friend(std::string_view name) {
77    // With Boost.PFR you can put data directly into user provided structures
78    user_info info = db::one_row_as<user_info>(
79        "SELECT id, name, email, login FROM user_infos WHERE name=$0",
80        name
81    );
82
83    ////////////////// No boilerplate code to move data around /////////////////////
84
85
86
87
88
89
90    ////////////////////////////////////////////////////////////////////////////////
91
92    auto friend_info = ask_user_for_friend(std::move(info));
93
94    db::insert(
95        "INSERT INTO user_infos(id, name, email, login) VALUES ($0, $1, $2, $3)",
96        friend_info     ////////////////////////////////////////////////////////////
97                        // Boost.PFR allows you to iterate over all the fields of a
98                        // user provided structure
99                        //
100    );
101
102    return friend_info;
103}
104```
105]]
106]
107
108Otherwise your library could require a customization point for a user type:
109
110
111[table:hand_made_vs_pfr_2
112[[ Without Boost.PFR ] [ With Boost.PFR ]]
113[[
114```
115#include <db/api.hpp>
116
117struct user_info {
118    std::int64_t id;
119    std::string name, email, login;
120};
121
122/// Customizations via hand-written code or macro like BOOST_FUSION_ADAPT_STRUCT ///
123auto db_api_tie(user_info& ui) noexcept {
124    return std::tie(ui.id, ui.name, ui.email, ui.login);
125}
126
127auto db_api_tie(const user_info& ui) noexcept {
128    return std::tie(ui.id, ui.name, ui.email, ui.login);
129}
130////////////////////////////////////////////////////////////////////////////////////
131```
132][
133```
134#include <db/api.hpp>
135
136struct user_info {
137    std::int64_t id;
138    std::string name, email, login;
139};
140
141//////// With Boost.PFR there's no need in hand written customizations /////////////
142
143
144
145
146
147
148
149////////////////////////////////////////////////////////////////////////////////////
150```
151]]
152]
153
154
155With Boost.PFR the code is shorter, more readable and more pleasant to write.
156
157
158
159[h2 Out of the box functionality ]
160
161Boost.PFR adds the following out-of-the-box functionality for aggregate initializable structures:
162
163* comparison functions
164* heterogeneous comparators
165* hash
166* IO streaming
167* access to members by index
168* member type retrieval
169* methods for cooperation with `std::tuple`
170* methods to visit each field of the structure
171
172Boost.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 github] into your project, and the library will work fine.
173
174[caution Recommended C++ Standards are C++17 and above. Library requires at least C++14! Pre C++14 compilers (C++11, C++03...) are not supported]
175
176[endsect]
177
178
179[section Short Examples for the Impatient]
180
181[import ../example/quick_examples.cpp]
182
183
184[table:quick_examples
185[[ Code snippet ] [ Reference: ]]
186[
187    [ [pfr_quick_examples_get] ]
188    [ [funcref boost::pfr::get] ]
189][
190    [ [pfr_quick_examples_ops] ]
191    [
192
193        [headerref boost/pfr/ops.hpp Header boost/pfr/ops.hpp]:
194
195        * [funcref boost::pfr::eq]
196
197        * [funcref boost::pfr::ne]
198
199        * [funcref boost::pfr::gt]
200
201        * ...
202
203    ]
204][
205    [ [pfr_quick_examples_for_each] ]
206    [
207        [funcref boost::pfr::for_each_field]
208
209        [funcref boost::pfr::io]
210    ]
211][
212    [ [pfr_quick_examples_functions_for] ]
213    [ [macroref BOOST_PFR_FUNCTIONS_FOR] ]
214][
215    [ [pfr_quick_examples_eq_fields] ]
216    [
217        [headerref boost/pfr/ops_fields.hpp Header boost/pfr/ops_fields.hpp ]:
218
219        * [funcref boost::pfr::eq_fields]
220
221        * [funcref boost::pfr::ne_fields]
222
223        * [funcref boost::pfr::gt_fields]
224
225        * ...
226
227        [headerref boost/pfr/io_fields.hpp Header boost/pfr/io_fields.hpp ]
228
229        * [funcref boost::pfr::io_fields]
230
231    ]
232][
233    [ [pfr_quick_examples_for_each_idx] ]
234    [ [funcref boost::pfr::for_each_field] ]
235][
236    [ [pfr_quick_examples_tuple_size] ]
237    [ [classref boost::pfr::tuple_size] ]
238][
239    [ [pfr_quick_examples_structure_to_tuple] ]
240    [ [funcref boost::pfr::structure_to_tuple] ]
241][
242    [ [pfr_quick_examples_structure_tie] ]
243    [ [funcref boost::pfr::structure_tie] ]
244]]
245
246
247
248[endsect]
249
250
251[section Tutorial]
252
253[import ../example/sample_printing.cpp]
254[import ../example/get.cpp]
255
256
257[section Why tuples are bad and aggregates are more preferable?]
258
259`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:
260
261[table:tuples_vs_aggregates
262[[ Tuple ] [ Aggregate ]]
263[[
264```
265using auth_info_tuple = std::tuple<
266    std::int64_t, // What does this integer represents?
267    std::int64_t,
268    std::time_t
269>;
270```
271][
272```
273struct auth_info_aggregate {
274    std::int64_t user_id;    // Oh, now I see!
275    std::int64_t session_id;
276    std::time_t  valid_till;
277};
278```
279]]
280]
281
282Definition via aggregate initializable structure is much more clear. Same story with usages: `return std::get<1>(value);` vs. `return value.session_id;`.
283
284Another advantage of aggregates is a more efficient copy, move construction and assignments.
285
286Because of the above issues some guidelines recommend to [*use aggregates instead of tuples]. However aggregates fail when it comes to the functional like programming.
287
288Boost.PFR library [*provides tuple like methods for aggregate initializable structures], making aggregates usable in contexts where only tuples were useful.
289
290[endsect]
291
292[section Accessing structure member by index] [pfr_example_get] [endsect]
293[section Custom printing of aggregates] [pfr_sample_printing] [endsect]
294
295
296[section Three ways of getting operators ]
297
298There 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.
299
300[table:ops_comp Different approaches for operators
301    [[ Approach
302    ][ When to use
303    ][ 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 ]]
304
305    [[
306        [headerref boost/pfr/ops.hpp boost/pfr/ops.hpp: eq, ne, gt, lt, le, ge]
307
308        [headerref boost/pfr/io.hpp boost/pfr/io.hpp: io]
309    ][
310        Use when you need to compare values by provided for them operators or via field-by-field comparison.
311    ][ no ][ yes ][ yes ][ no ][ yes ]]
312
313    [[
314        [macroref BOOST_PFR_FUNCTIONS_FOR BOOST_PFR_FUNCTIONS_FOR(T)]
315    ][
316        Use near the type definition to define the whole set of operators for your type.
317    ][ yes ][ no ][ no ][ yes for T ] [ no (compile time error) ]]
318
319    [[
320        [headerref boost/pfr/ops_fields.hpp boost/pfr/ops_fields.hpp: eq_fields, ne_fields, gt_fields, lt_fields, le_fields, ge_fields]
321
322        [headerref boost/pfr/io.hpp boost/pfr/io_fields.hpp: io_fields]
323    ][
324        Use to implement the required set of operators for your type.
325    ][ no ][ yes ][ yes ][ yes ][ yes ]]
326]
327
328More detailed description follows:
329
330[*1. `eq, ne, gt, lt, le, ge, io` approach]
331
332This 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:
333
334```
335#include <boost/pfr/ops.hpp>
336
337template <class T>
338struct uniform_comparator_less {
339    bool operator()(const T& lhs, const T& rhs) const noexcept {
340        // If T has operator< or conversion operator then it is used.
341        return boost::pfr::lt(lhs, rhs);
342    }
343};
344```
345This methods effects are local to the function. It works even for local types, like structures defined in functions.
346
347
348[*2. BOOST_PFR_FUNCTIONS_FOR(T) approach]
349
350This method is good if you're writing a structure and wish to define operators for that structure.
351```
352#include <boost/pfr/functions_for.hpp>
353
354struct pair_like {
355    int first;
356    short second;
357};
358
359BOOST_PFR_FUNCTIONS_FOR(pair_like)   // Defines operators
360
361// ...
362
363assert(pair_like{1, 2} < pair_like{1, 3});
364```
365Argument Dependant Lookup works well. `std::less` will find the operators for `struct pair_like`. [macroref BOOST_PFR_FUNCTIONS_FOR BOOST_PFR_FUNCTIONS_FOR(T)]
366can not be used for local types. It does not respect conversion operators of `T`, so for example the following code
367will output different values:
368```
369#include <boost/pfr/functions_for.hpp>
370
371struct empty {
372    operator std::string() { return "empty{}"; }
373};
374// Uncomment to get different output:
375// BOOST_PFR_FUNCTIONS_FOR(empty)
376
377// ...
378std::cout << empty{}; // Outputs `empty{}` if BOOST_PFR_FUNCTIONS_FOR(empty) is commented out, '{}' otherwise.
379```
380
381[*3. `eq_fields, ne_fields, gt_fields, lt_fields, le_fields, ge_fields, io_fields` approach]
382
383This method is good if you're willing to provide only some operators for your type:
384
385```
386#include <boost/pfr/io_fields.hpp>
387
388struct pair_like {
389    int first;
390    std::string second;
391};
392
393inline std::ostream& operator<<(std::ostream& os, const pair_like& x) {
394    return os <<  bost::pfr::io_fields(x);
395}
396```
397
398All 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.
399
400[endsect]
401
402
403[section Reflection of unions ]
404
405You could use tuple-like representation if a type contains union. But be sure that operations for union are manually defined:
406
407```
408#include <boost/pfr/ops.hpp>
409
410union test_union {
411    int i;
412    float f;
413};
414
415inline bool operator==(test_union l, test_union r) noexcept; // Compile time error without this operator
416
417bool some_function(test_union f1, test_union f2) {
418    return boost::pfr::eq(f1, f2); // OK
419}
420
421```
422
423Reflection 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.
424
425Any attempt to reflect unions leads to a compile time error. In many cases a static assert is triggered that outputs the following message:
426
427```
428error: static_assert failed "====================> Boost.PFR: For safety reasons it is forbidden
429        to reflect unions. See `Reflection of unions` section in the docs for more info."
430```
431
432[endsect]
433
434[endsect]
435
436
437[section Limitations and Configuration]
438
439[caution Recommended C++ Standards are C++17 and above. Library requires at least C++14! Pre C++14 compilers (C++11, C++03...) are not supported. ]
440
441Boost.PFR library works with types that satisfy the requirements of `SimpleAggregate`: aggregate types without base classes, `const` fields, references, or C arrays:
442
443```
444struct simple_aggregate {  // SimpleAggregate
445    std::string name;
446    int age;
447    boost::uuids::uuid uuid;
448};
449
450struct empty {             // SimpleAggregate
451};
452
453struct aggregate : empty { // not a SimpleAggregate
454    std::string name;
455    int age;
456    boost::uuids::uuid uuid;
457};
458```
459The library may work with aggregates that don't satisfy the requirements of `SimpleAggregate`, but the behavior tends to be non-portable.
460
461[h2 Configuration Macro]
462
463By 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:
464[table:linkmacro Macros
465    [[Macro name] [Effect]]
466    [[*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.]]
467    [[*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.]]
468    [[*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. ]]
469]
470
471
472[h2 Details on Limitations]
473
474The Boost.PFRs reflection has some limitations that depend on a C++ Standard and compiler capabilities:
475
476* Static variables are ignored
477* T must be aggregate initializable without empty base classes
478* if T contains C arrays or it is inherited from non-empty type then the result of reflection may differ depending on the C++ version and library configuration
479* Additional limitations if [*BOOST_PFR_USE_CPP17 == 0]:
480    * Non of the member fields should have a template constructor from one parameter.
481    * Additional limitations if [*BOOST_PFR_USE_LOOPHOLE == 0]:
482        * T must be constexpr aggregate initializable and all its fields must be constexpr default constructible
483        * [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.
484
485
486[endsect]
487
488[section How it works]
489
490Short description:
491
492# at compile-time: use aggregate initialization to detect fields count in user-provided structure
493    * [*BOOST_PFR_USE_CPP17 == 1]:
494        # at compile-time: structured bindings are used to decompose a type `T` to known amount of fields
495    * [*BOOST_PFR_USE_CPP17 == 0 && BOOST_PFR_USE_LOOPHOLE == 1]:
496        # at compile-time: use aggregate initialization to detect fields count in user-provided structure
497        # 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
498        # at compile-time: using knowledge from previous steps create a tuple with exactly the same layout as in user-provided structure
499        # at compile-time: find offsets for each field in user-provided structure using the tuple from previous step
500        # at run-time: get pointer to each field, knowing the structure address and each field offset
501        # at run-time: a tuple of references to fields is returned => all the tuple methods are available for the structure
502    * [*BOOST_PFR_USE_CPP17 == 0 && BOOST_PFR_USE_LOOPHOLE == 0]:
503        # at compile-time: let `I` be is an index of current field, it equals 0
504        # 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.]
505        # at compile-time: `I += 1`
506        # 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
507        # at compile-time: using knowledge from previous steps create a tuple with exactly the same layout as in user-provided structure
508        # at compile-time: find offsets for each field in user-provided structure using the tuple from previous step
509        # at run-time: get pointer to each field, knowing the structure address and each field offset
510# at run-time: a tuple of references to fields is returned => all the tuple methods are available for the structure
511
512Long description of some basics: [@https://youtu.be/UlNUNxLtBI0 Antony Polukhin: Better C++14 reflections].
513Long 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].
514Description of the [*BOOST_PFR_USE_LOOPHOLE == 1] technique by its inventor Alexandr Poltavsky [@http://alexpolt.github.io/type-loophole.html in his blog].
515
516[endsect]
517
518[section Acknowledgements]
519
520Many thanks to Bruno Dutra for showing the technique to precisely reflect aggregate initializable type in C++14 [@https://github.com/apolukhin/magic_get/issues/5 Manual type registering/structured bindings might be unnecessary].
521
522Many 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].
523
524Many thanks to Chris Beck for implementing the detect-offsets-and-get-field-address functionality that avoids Undefined Behavior of reinterpret_casting layout compatible structures.
525
526Many thanks to the Boost people who participated in the formal review, especially to Benedek Thaler, Steven Watanabe and Andrzej Krzemienski.
527
528[endsect]
529
530[xinclude autodoc_pfr.xml]
531