Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Field's names functionality #129

Merged
merged 27 commits into from
Sep 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4a1defa
Add methods to extract fields names
denzor200 Jun 25, 2023
e8e077c
fix for cxx14 build
denzor200 Jun 26, 2023
f09357c
fix lint issues
denzor200 Jun 27, 2023
454947d
review
denzor200 Jun 28, 2023
03e1d76
Merge remote-tracking branch 'origin/develop' into feature/get_name
denzor200 Aug 11, 2023
fe5a70b
Merge remote-tracking branch 'origin/develop' into feature/get_name
denzor200 Aug 12, 2023
196aeb6
Fix lint issue about nonascii symbol
denzor200 Aug 12, 2023
50c9d6f
Fix strip_boost_namespace.sh
denzor200 Aug 12, 2023
b15196c
Fix MSVC
denzor200 Aug 13, 2023
6f544ce
Add Clang support
denzor200 Aug 19, 2023
6e23ed5
Fix nonascii fields
denzor200 Aug 19, 2023
a5b9cd5
Add test for big structures
denzor200 Aug 24, 2023
9b6a0de
Refactoring for parser
denzor200 Aug 26, 2023
efd25e9
review
denzor200 Aug 26, 2023
9b2817a
Parsing ala boost type_index
denzor200 Aug 28, 2023
fcfca74
Write docs
denzor200 Sep 2, 2023
dbbfa6e
Parser might be explicitly tagged as backward
denzor200 Sep 2, 2023
2c79ac7
Fix CI
denzor200 Sep 2, 2023
0cb5cf2
Fix strip_boost_namespace.sh
denzor200 Sep 7, 2023
5a7d652
Fix docs
denzor200 Sep 9, 2023
dd8a527
Rename C++20 features detectors
denzor200 Sep 9, 2023
8794056
review
denzor200 Sep 9, 2023
6dcf66c
Fix for old MSVC compiler
denzor200 Sep 10, 2023
3f07e71
relax standard library requirements
apolukhin Sep 17, 2023
41e87fb
Do not require Python to run tests
apolukhin Sep 17, 2023
27b9706
Fix compilation on MSVC
apolukhin Sep 17, 2023
9cc76bc
Suppress non-ASCII warning from boost-inspect
apolukhin Sep 17, 2023
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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
fail-fast: false
matrix:
include:
- toolset: gcc-12
- toolset: gcc-12 # Do not remove! It is the only toolset that tests misc/strip_boost_namespace.sh
cxxstd: "03,11,14,17,2a"
os: ubuntu-22.04
cxxflags: "cxxflags=--coverage -fsanitize=address,leak,undefined -fno-sanitize-recover=undefined"
Expand Down Expand Up @@ -94,7 +94,7 @@ jobs:
dist/bin/inspect libs/$LIBRARY

- name: Test boost namespace stripping
if: ${{matrix.toolset == 'gcc-9'}}
if: ${{matrix.toolset == 'gcc-12'}}
run: ../boost-root/libs/$LIBRARY/misc/strip_boost_namespace.sh

- name: Prepare coverage data
Expand Down
1 change: 1 addition & 0 deletions doc/Jamfile.v2
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ local doxygen_params =
<doxygen:param>"ALIASES= \\
\"forcedlink{1}=\\xmlonly<link linkend='boost.pfr.\\1'>\\endxmlonly boost::pfr::\\1\\xmlonly</link>\\endxmlonly\" \\
\"podops=\\b See \\b Also : \\xmlonly<link linkend='boost_pfr.tutorial.three_ways_of_getting_operators'>\\endxmlonly 'Three ways of getting operators' \\xmlonly</link>\\endxmlonly\" \\
\"fnrefl=\\b See \\b Also : \\xmlonly<link linkend='boost_pfr.tutorial.reflection_of_field_names'>\\endxmlonly 'Reflection of field names' \\xmlonly</link>\\endxmlonly\" \\
\"customio=\\b See \\b Also : \\xmlonly<link linkend='boost_pfr.tutorial.custom_printing_of_aggregates'>\\endxmlonly 'Custom printing of aggregates' \\xmlonly</link>\\endxmlonly for info on how to implement your own manipulator with custom format.\" \\
\"aggregate=\\xmlonly<link linkend='boost_pfr.limitations_and_configuration'>\\endxmlonly simple aggregate \\xmlonly</link>\\endxmlonly\" \\
"
Expand Down
125 changes: 122 additions & 3 deletions doc/pfr.qbk
Original file line number Diff line number Diff line change
Expand Up @@ -181,14 +181,16 @@ Boost.PFR adds the following out-of-the-box functionality for aggregate initiali
* hash
* IO streaming
* access to members by index or type
* access to member's names by index
denzor200 marked this conversation as resolved.
Show resolved Hide resolved
* member type retrieval
* methods for cooperation with `std::tuple`
* 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++17 and above. Library requires at least C++14! Pre C++14 compilers (C++11, C++03...) are not supported]
[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]

Expand All @@ -203,6 +205,9 @@ Boost.PFR is a header only library that does not depend on Boost. You can just c
[
[ [pfr_quick_examples_get] ]
[ [funcref boost::pfr::get] ]
][
[ [pfr_quick_examples_get_name] ]
[ [funcref boost::pfr::get_name] ]
][
[ [pfr_quick_examples_ops] ]
[
Expand Down Expand Up @@ -269,6 +274,7 @@ Boost.PFR is a header only library that does not depend on Boost. You can just c

[import ../example/sample_printing.cpp]
[import ../example/get.cpp]
[import ../example/get_name.cpp]


[section Why tuples are bad and aggregates are more preferable?]
Expand Down Expand Up @@ -448,12 +454,21 @@ error: static_assert failed "====================> Boost.PFR: For safety reasons

[endsect]


[section Reflection of field names ]

[pfr_example_get_name]
denzor200 marked this conversation as resolved.
Show resolved Hide resolved

See [link boost_pfr.limitations_of_field_names_refle [*Limitations of field names reflection]] and [link boost_pfr.limitations_and_configuration [*Limitations and Configuration]].

[endsect]

[endsect]


[section Limitations and Configuration]

[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. ]
[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:

Expand All @@ -475,6 +490,28 @@ struct aggregate : empty { // not a SimpleAggregate
```
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 a `SimpleAggregate` which variables are able to be declared in any other translation unit.
It's better not to use this feature with anonymous structure, local structure or a structure defined inside anonymous namespace.

```
struct external_simple_aggregate {
std::string name;
int age;
boost::uuids::uuid uuid;
};
auto v1 = external_simple_aggregate{}; // can be declared outside via `extern`

struct {
std::string name;
int age;
boost::uuids::uuid uuid;
} anonymous; // can't be declared outside
```
Field's name extraction may work with a `SimpleAggregate` that does't satisfy such requirements, but the behavior tends to be non-portable.
Try using `-fpermissive` if you have any issue with it.

As you see above extraction of field name feature has requirements of input type, additionally it has some requirements of compiler, see [link boost_pfr.limitations_of_field_names_refle [*Limitations of field names reflection]] for details.

[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:
Expand All @@ -485,6 +522,9 @@ By default Boost.PFR [*auto-detects your compiler abilities] and automatically d
[[*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's names extracting 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's name. ]]
[[*BOOST_PFR_FUNCTION_SIGNATURE*] [On platforms which are unknown by Boost.PFR library, the 'boost/pfr/config.hpp' header defines the BOOST_PFR_FUNCTION_SIGNATURE macro equal to "". Defining this macro before including the header might help the library to work on your specific platform. See details [link boost_pfr.limitations_of_field_names_refle [*here]]. ]]
[[*BOOST_PFR_CORE_NAME_PARSING*] [On platforms which are unknown by Boost.PFR library, the 'boost/pfr/config.hpp' header defines the BOOST_PFR_CORE_NAME_PARSING macro equal to (0,0,false). Defining this macro before including the header might help the library to work on your specific platform. See details [link boost_pfr.limitations_of_field_names_refle [*here]]. ]]
[[*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. ]]
]

Expand All @@ -502,6 +542,85 @@ The Boost.PFRs reflection has some limitations that depend on a C++ Standard and
* 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 must be able to be extern.

[endsect]


[section Limitations of field names reflection]

Boost.PFRs extraction of field name has been tested and successfully work on many compilers.

[section Define the BOOST_PFR_FUNCTION_SIGNATURE macro]

If you get the following error during compilation
```
error: static_assert failed "====================> Boost.PFR: Extraction of field name could not detect your compiler.
Please make the BOOST_PFR_FUNCTION_SIGNATURE macro use
correct compiler macro for getting the whole function name.
Define BOOST_PFR_CORE_NAME_PARSING to correct value after that."
```
then you are using a compiler that was not tested with this library.

BOOST_PFR_FUNCTION_SIGNATURE must be defined to a compiler specific macro, that outputs the *whole*
function signature including non-type template parameters.


[endsect]

[section Fixing get_name() output]

Let's assume the structure `namespace A { struct A { int fn; }; }`

If the output of `boost::pfr::get_name<0, A::A>()`
returns not just `fn` but also a lot of text around the `fn`
or does not return name at all
then you are using a compiler that was not tested with this library and you need to setup the
BOOST_PFR_CORE_NAME_PARSING macro.

Here is a short instruction:

# get the output of `boost::pfr::get_name<0, A::A>()`
# define BOOST_PFR_CORE_NAME_PARSING to
`(skip_at_begin, skip_at_end, false, "")`, where
* `skip_at_begin` is equal to characters count before the first occurrence of `fn` in output
* `skip_at_end` is equal to characters count after last occurrence of `fn` in output
# check that `boost::pfr::get_name<0, A::A>()` returns "fn"
# if it does not return `fn`, then define BOOST_PFR_CORE_NAME_PARSING to
`(skip_at_begin, skip_at_end, true, "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 `fn` in output
# (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.


Consider the following example:

`boost::pfr::get_name<0, A::A>()` returns
"auto __cdecl boost::pfr::detail::name_of_field_impl<struct A::A,&a->fn>(void) noexcept" while compiled with `-DBOOST_PFR_CORE_NAME_PARSING (0,0,false,"")`. Then you shall set
`skip_at_begin` to `sizeof("auto __cdecl boost::pfr::detail::name_of_field_impl<") - 1`
and `skip_at_end` to `sizeof(">(void) noexcept") - 1` and last parameter of macro to `"->"`.

``
#define BOOST_PFR_CORE_NAME_PARSING (52, 16, true, "->")
``

Another example:

`boost::pfr::get_name<0, A::A>()` returns
"consteval auto boost::pfr::detail::name_of_field_impl() [with MsvcWorkaround = A::A; auto ptr = (& a.A::A::fn)]" while compiled with `-DBOOST_PFR_CORE_NAME_PARSING (0,0,false,"")`. Then you shall set
`skip_at_begin` to `0`
and `skip_at_end` to `sizeof(")]") - 1` and last parameter of macro to `backward("::")`.

``
#define BOOST_PFR_CORE_NAME_PARSING (0, 2, true, backward("::"))
``

[endsect]

[endsect]

Expand Down
43 changes: 43 additions & 0 deletions example/get_name.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) 2023 Bela Schaum, X-Ryl669, Denis Mikhailov.
//
// 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)


// Initial implementation by Bela Schaum, https://github.com/schaumb
// The way to make it union and UB free by X-Ryl669, https://github.com/X-Ryl669
//

#include <boost/pfr/config.hpp>

#if BOOST_PFR_CORE_NAME_ENABLED && BOOST_PFR_USE_CPP17
//[pfr_example_get_name
/*`
Since C++20 it's possible to read name of structure fields by index using Boost.PFR library.
The following example shows how to do it using [funcref boost::pfr::get_name].

Let's define some structure:
*/
#include <boost/pfr/core_name.hpp>

struct foo { // defining structure
int some_integer;
char c;
};

/*`
We can access field's names of that structure by index:
*/
constexpr auto r1 = boost::pfr::get_name<0, foo>(); // reading name of field with index 0, returns string `some_integer`
constexpr auto r2 = boost::pfr::get_name<1, foo>(); // reading name of field with index 1, returns string `c`
//] [/pfr_example_get_name]
#endif

int main() {
#if BOOST_PFR_CORE_NAME_ENABLED && BOOST_PFR_USE_CPP17
if (r1 != "some_integer") return 1;
if (r2 != "c") return 2;
#endif

return 0;
}
17 changes: 17 additions & 0 deletions example/quick_examples.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,23 @@ void test_examples() {
//]
}

// Disabled from testing since it's unportable
#if 0
{
//[pfr_quick_examples_get_name
// Get name of field by index

struct sample {
int f1;
long f2;
};

std::cout << boost::pfr::get_name<0, sample>()
<< boost::pfr::get_name<1, sample>(); // Outputs: f1 f2
//]
}
#endif

#if BOOST_PFR_USE_CPP17 || BOOST_PFR_USE_LOOPHOLE
{
//[pfr_quick_examples_structure_to_tuple
Expand Down
1 change: 1 addition & 0 deletions include/boost/pfr.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

#include <boost/pfr/config.hpp>
#include <boost/pfr/core.hpp>
#include <boost/pfr/core_name.hpp>
#include <boost/pfr/functions_for.hpp>
#include <boost/pfr/functors.hpp>
#include <boost/pfr/io.hpp>
Expand Down
41 changes: 41 additions & 0 deletions include/boost/pfr/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,47 @@
# endif
#endif

#ifndef BOOST_PFR_CORE_NAME_ENABLED
# if (__cplusplus >= 202002L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 202002L))
# if (defined(__cpp_nontype_template_args) && __cpp_nontype_template_args >= 201911) \
|| (defined(__clang_major__) && __clang_major__ >= 12)
# define BOOST_PFR_CORE_NAME_ENABLED 1
# else
# define BOOST_PFR_CORE_NAME_ENABLED 0
# endif
# else
# define BOOST_PFR_CORE_NAME_ENABLED 0
# endif
#endif

#ifndef BOOST_PFR_FUNCTION_SIGNATURE
# if defined(__FUNCSIG__)
# define BOOST_PFR_FUNCTION_SIGNATURE __FUNCSIG__
# elif defined(__PRETTY_FUNCTION__) \
|| defined(__GNUC__) \
|| defined(__clang__)
# define BOOST_PFR_FUNCTION_SIGNATURE __PRETTY_FUNCTION__
# else
# define BOOST_PFR_FUNCTION_SIGNATURE ""
# endif
#endif

#ifndef BOOST_PFR_CORE_NAME_PARSING
# if defined(_MSC_VER)
// sizeof("auto __cdecl boost::pfr::detail::name_of_field_impl<") - 1, sizeof(">(void) noexcept") - 1
# define BOOST_PFR_CORE_NAME_PARSING (52 /*45 for non boost*/, 16, backward("->"))
# elif defined(__clang__)
// sizeof("auto boost::pfr::detail::name_of_field_impl() [MsvcWorkaround = ") - 1, sizeof("}]") - 1
# define BOOST_PFR_CORE_NAME_PARSING (64 /*57 for non boost*/, 2, backward("."))
# elif defined(__GNUC__)
// sizeof("consteval auto boost::pfr::detail::name_of_field_impl() [with MsvcWorkaround = ") - 1, sizeof(")]") - 1
# define BOOST_PFR_CORE_NAME_PARSING (79 /*72 for non boost*/, 2, backward("::"))
# else
// Deafult parser for other platforms... Just skip nothing!
# define BOOST_PFR_CORE_NAME_PARSING (0, 0, "")
# endif
#endif

#if defined(__has_cpp_attribute)
# if __has_cpp_attribute(maybe_unused)
# define BOOST_PFR_MAYBE_UNUSED [[maybe_unused]]
Expand Down
Loading
Loading