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