Skip to content

Commit

Permalink
Parsing ala boost type_index
Browse files Browse the repository at this point in the history
  • Loading branch information
denzor200 committed Aug 30, 2023
1 parent efd25e9 commit 9b2817a
Show file tree
Hide file tree
Showing 9 changed files with 196 additions and 33 deletions.
9 changes: 9 additions & 0 deletions doc/pfr.qbk
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,8 @@ By default Boost.PFR [*auto-detects your compiler abilities] and automatically d
[[*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_ENABLE_GET_NAME_STATIC*] [On platforms where field's names extracting is not supported, the 'boost/pfr/config.hpp' header defines the BOOST_PFR_ENABLE_GET_NAME_STATIC 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*] [TODO: desc it ]]
[[*BOOST_PFR_CORE_NAME_PARSING*] [TODO: desc it ]]
[[*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 @@ -528,6 +530,13 @@ The Boost.PFRs reflection has some limitations that depend on a C++ Standard and

[endsect]


[section Limitations of field's names reflection]

TODO: write the article and make a link on it inside "Limitations and Configuration"

[endsect]

[section How it works]

Short description:
Expand Down
41 changes: 34 additions & 7 deletions include/boost/pfr/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,18 +99,45 @@
#endif

#ifndef BOOST_PFR_ENABLE_GET_NAME_STATIC
# if (defined(__cpp_nontype_template_args) && __cpp_nontype_template_args >= 201911) || (__cplusplus >= 202002L && defined(__clang_major__) && __clang_major__ >= 12)
// Only these 3 compilers have a macro to extract func name
# if defined(__clang__) || defined(__GNUC__) || defined(_MSC_VER)
# define BOOST_PFR_ENABLE_GET_NAME_STATIC 1
# else
# define BOOST_PFR_ENABLE_GET_NAME_STATIC 0
# endif
# if (defined(__cpp_nontype_template_args) && __cpp_nontype_template_args >= 201911) \
|| (__cplusplus >= 202002L && defined(__clang_major__) && __clang_major__ >= 12)
# define BOOST_PFR_ENABLE_GET_NAME_STATIC 1
# else
# define BOOST_PFR_ENABLE_GET_NAME_STATIC 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
// TODO: specify in the doc that this is unsupported value
# 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, 16, "->")
# elif defined(__clang__)
// sizeof("auto boost::pfr::detail::name_of_field_impl() [MsvcWorkaround = ") - 1, sizeof("}]") - 1
# define BOOST_PFR_CORE_NAME_PARSING (64, 2, ".")
# 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, 2, "::")
# else
// TODO: specify in the doc that this is unsupported value
// TODO: .. and even if value is supported, there still no gurantee that it correct! also make a compile-fail test for such case
// 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
4 changes: 2 additions & 2 deletions include/boost/pfr/detail/core_name14_disabled.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ template <class T, std::size_t I>
constexpr auto get_name() noexcept {
static_assert(
sizeof(T) && false,
"====================> Boost.PFR: Field's names extracting functionality requires C++20 and compiler that supports __PRETTY_FUNCTION__ or __FUNCSIG__ macro (GCC, Clang or MSVC)."
"====================> Boost.PFR: Field's names extracting functionality requires C++20."
);

return nullptr;
Expand All @@ -31,7 +31,7 @@ template <class T>
constexpr auto tie_as_names_tuple() noexcept {
static_assert(
sizeof(T) && false,
"====================> Boost.PFR: Field's names extracting functionality requires C++20 and compiler that supports __PRETTY_FUNCTION__ or __FUNCSIG__ macro (GCC, Clang or MSVC)."
"====================> Boost.PFR: Field's names extracting functionality requires C++20."
);

return detail::sequence_tuple::make_sequence_tuple();
Expand Down
86 changes: 63 additions & 23 deletions include/boost/pfr/detail/core_name20_static.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,40 +17,80 @@
#include <boost/pfr/detail/sequence_tuple.hpp>
#include <boost/pfr/detail/make_integer_sequence.hpp>
#include <boost/pfr/detail/fields_count.hpp>
#include <boost/pfr/detail/stdarray.hpp>
#include <type_traits>
#include <string_view>
#include <array>
#include <algorithm> // for std::ranges::copy

namespace boost { namespace pfr { namespace detail {

consteval std::string_view name_of_field_parse(std::string_view sv,
std::size_t size_at_begin,
std::size_t size_at_end,
std::string_view until_runtime_last) noexcept {
sv.remove_prefix(size_at_begin);
sv.remove_suffix(size_at_end);
return sv.substr(sv.rfind(until_runtime_last) + until_runtime_last.size());
struct core_name_skip {
std::size_t size_at_begin;
std::size_t size_at_end;
bool more_at_runtime;
std::string_view until_runtime_last;
};

consteval core_name_skip make_core_name_skip(std::size_t size_at_begin,
std::size_t size_at_end,
bool more_at_runtime,
std::string_view until_runtime_last) noexcept
{
return core_name_skip{size_at_begin, size_at_end, more_at_runtime, until_runtime_last};
}

consteval core_name_skip make_core_name_skip(std::size_t size_at_begin,
std::size_t size_at_end,
std::string_view until_runtime_last) noexcept
{
return core_name_skip{size_at_begin, size_at_end, true, until_runtime_last};
}

consteval std::string_view apply_core_name_skip(std::string_view sv,
core_name_skip s) noexcept {
sv.remove_prefix((std::min)(s.size_at_begin, sv.size()));
sv.remove_suffix((std::min)(s.size_at_end, sv.size()));
return s.more_at_runtime ? sv.substr((std::min)(sv.rfind(s.until_runtime_last) + s.until_runtime_last.size(), sv.size()))
: sv;
;
}

template <bool Condition>
consteval void assert_compile_time_legths() noexcept {
static_assert(
Condition,
"PFRs extraction of field name is misconfigured for your compiler. "
"Please define BOOST_PFR_CORE_NAME_PARSING to correct values. See section "
"Limitations of field's names reflection' of the documentation for more information."
);
}

template <class T>
consteval void failed_to_get_function_name() noexcept {
static_assert(
sizeof(T) && false,
"PFRs 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."
);
}

template <typename MsvcWorkaround, auto ptr>
consteval auto name_of_field_impl() noexcept {
#ifdef _MSC_VER
constexpr auto sv = __FUNCSIG__;
// sizeof("auto __cdecl boost::pfr::detail::name_of_field_impl<") - 1, sizeof(">(void) noexcept") - 1
constexpr auto fn = detail::name_of_field_parse(sv, 52, 16, "->");
#elif __clang__
constexpr auto sv = __PRETTY_FUNCTION__;
// sizeof("auto boost::pfr::detail::name_of_field_impl() [MsvcWorkaround = ") - 1, sizeof("}]") - 1
constexpr auto fn = detail::name_of_field_parse(sv, 64, 2, ".");
#else // GCC
constexpr auto sv = __PRETTY_FUNCTION__;
// sizeof("consteval auto boost::pfr::detail::name_of_field_impl() [with MsvcWorkaround = ") - 1, sizeof(")]") - 1
constexpr auto fn = detail::name_of_field_parse(sv, 79, 2, "::");
#endif
auto res = std::array<char, fn.size()+1>{};
std::ranges::copy(fn, res.begin());
return res;
constexpr std::string_view sv = BOOST_PFR_FUNCTION_SIGNATURE;
if constexpr (sv.empty()) {
detail::failed_to_get_function_name<MsvcWorkaround>();
return detail::make_stdarray<char>(0);
} else {
constexpr auto skip = detail::make_core_name_skip BOOST_PFR_CORE_NAME_PARSING;
constexpr auto fn = detail::apply_core_name_skip(sv, skip);
auto res = std::array<char, fn.size()+1>{};
detail::assert_compile_time_legths<!fn.empty()>();
std::ranges::copy(fn, res.begin());
return res;
}
}

template <typename T>
Expand Down
3 changes: 3 additions & 0 deletions test/config/print_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#include <boost/pfr/config.hpp> // inclusion of an another PFR header may fail when BOOST_PFR_ENABLED=0
#include <boost/preprocessor/stringize.hpp>

#include <iostream>

Expand All @@ -15,6 +16,8 @@ int main() {
<< "BOOST_PFR_HAS_GUARANTEED_COPY_ELISION == " << BOOST_PFR_HAS_GUARANTEED_COPY_ELISION << '\n'
<< "BOOST_PFR_ENABLE_IMPLICIT_REFLECTION == " << BOOST_PFR_ENABLE_IMPLICIT_REFLECTION << '\n'
<< "BOOST_PFR_ENABLE_GET_NAME_STATIC == " << BOOST_PFR_ENABLE_GET_NAME_STATIC << '\n'
<< "BOOST_PFR_FUNCTION_SIGNATURE == " << BOOST_PP_STRINGIZE(BOOST_PFR_FUNCTION_SIGNATURE) << '\n'
<< "BOOST_PFR_CORE_NAME_PARSING == " << BOOST_PP_STRINGIZE(BOOST_PFR_CORE_NAME_PARSING) << '\n'
<< "BOOST_PFR_ENABLED == " << BOOST_PFR_ENABLED << '\n'
<< "__cplusplus == " << __cplusplus << '\n'
#ifdef __cpp_structured_bindings
Expand Down
12 changes: 11 additions & 1 deletion test/core_name/Jamfile.v2
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ project
[ check-target-builds ../core_name//compiler_supports_cxx20_nontype_template_args : : [ check-target-builds ../core_name//compiler_supports_cxx20_clang_workaround : : <build>no ] ]
;

local ENABLED_ENGINE = <define>BOOST_PFR_ENABLE_GET_NAME_STATIC=1 ;
local DISABLED_ENGINE = <define>BOOST_PFR_ENABLE_GET_NAME_STATIC=0 ;



Expand All @@ -54,12 +56,20 @@ actions invoke_python_generator
make fields_names_nonascii.cpp : generate_fields_names_nonascii.cpp.py : @invoke_python_generator ;
make fields_names_big.cpp : generate_fields_names_big.cpp.py : @invoke_python_generator ;

test-suite pfr_tests
test-suite pfr_name_tests
:
[ run fields_names.cpp : : : : ]
[ run fields_names_constexpr.cpp : : : : ]
[ run fields_names_nonascii.cpp : : : <toolset>msvc:<cxxflags>"/utf-8" : ]
[ run fields_names_big.cpp : : : <toolset>msvc:<cxxflags>"/bigobj" : ]
[ run print_name.cpp : : : <test-info>always_show_run_output ]
;

for local source_file in [ glob ./compile-fail/*.cpp ]
{
local target_name = $(source_file[1]:B) ;
pfr_name_tests += [ compile-fail $(source_file) : $(ENABLED_ENGINE) : $(target_name)_on ] ;
pfr_name_tests += [ compile-fail $(source_file) : $(DISABLED_ENGINE) : $(target_name)_off ] ;
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// 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
//

#define BOOST_PFR_FUNCTION_SIGNATURE ""
#include <boost/pfr/core_name.hpp>

struct A { int field; };

int main() {
(void)boost::pfr::get_name<0, A>(); // Must be a compile time error
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// 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
//

#define BOOST_PFR_FUNCTION_SIGNATURE "dummy"
#define BOOST_PFR_CORE_NAME_PARSING (0,0,"")
#include <boost/pfr/core_name.hpp>

struct A { int field; };

int main() {
(void)boost::pfr::get_name<0, A>(); // Must be a compile time error
}


33 changes: 33 additions & 0 deletions test/core_name/print_name.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// 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 <iostream>

// This cpp file:
// * tests BOOST_PFR_CORE_NAME_PARSING macro
// * outputs full name of the function so that PFRs extraction of field name could be adjust to new compiler without requesting regression tester's help
#define BOOST_PFR_CORE_NAME_PARSING (0,0,false,"")
#include <boost/pfr/core_name.hpp>

namespace user_defined_namespace {
struct user_defined_class { int user_defined_field; };
}

int main()
{
using namespace boost::pfr;

std::cout << "user_defined_namespace::user_defined_class::user_defined_field: "
<< get_name<0, user_defined_namespace::user_defined_class>() << '\n';


return 0;
}

0 comments on commit 9b2817a

Please sign in to comment.