diff --git a/doc/pfr.qbk b/doc/pfr.qbk index d810eac2..30b327a3 100644 --- a/doc/pfr.qbk +++ b/doc/pfr.qbk @@ -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. ]] ] @@ -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: diff --git a/include/boost/pfr/config.hpp b/include/boost/pfr/config.hpp index 8c24982d..1fb55c99 100644 --- a/include/boost/pfr/config.hpp +++ b/include/boost/pfr/config.hpp @@ -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]] diff --git a/include/boost/pfr/detail/core_name14_disabled.hpp b/include/boost/pfr/detail/core_name14_disabled.hpp index 535da42f..115e8382 100644 --- a/include/boost/pfr/detail/core_name14_disabled.hpp +++ b/include/boost/pfr/detail/core_name14_disabled.hpp @@ -21,7 +21,7 @@ template 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; @@ -31,7 +31,7 @@ template 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(); diff --git a/include/boost/pfr/detail/core_name20_static.hpp b/include/boost/pfr/detail/core_name20_static.hpp index c08fd47b..5a074334 100644 --- a/include/boost/pfr/detail/core_name20_static.hpp +++ b/include/boost/pfr/detail/core_name20_static.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -24,33 +25,72 @@ 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 +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 +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 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{}; - 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(); + return detail::make_stdarray(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{}; + detail::assert_compile_time_legths(); + std::ranges::copy(fn, res.begin()); + return res; + } } template diff --git a/test/config/print_config.cpp b/test/config/print_config.cpp index cc49fba4..2b0dc6a4 100644 --- a/test/config/print_config.cpp +++ b/test/config/print_config.cpp @@ -4,6 +4,7 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) #include // inclusion of an another PFR header may fail when BOOST_PFR_ENABLED=0 +#include #include @@ -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 diff --git a/test/core_name/Jamfile.v2 b/test/core_name/Jamfile.v2 index 238f94f4..c081e401 100644 --- a/test/core_name/Jamfile.v2 +++ b/test/core_name/Jamfile.v2 @@ -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 : : no ] ] ; +local ENABLED_ENGINE = BOOST_PFR_ENABLE_GET_NAME_STATIC=1 ; +local DISABLED_ENGINE = BOOST_PFR_ENABLE_GET_NAME_STATIC=0 ; @@ -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 : : : msvc:"/utf-8" : ] [ run fields_names_big.cpp : : : msvc:"/bigobj" : ] + [ run print_name.cpp : : : 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 ] ; +} + diff --git a/test/core_name/compile-fail/fields_names_could_not_detect_compiler.cpp b/test/core_name/compile-fail/fields_names_could_not_detect_compiler.cpp new file mode 100644 index 00000000..894feb91 --- /dev/null +++ b/test/core_name/compile-fail/fields_names_could_not_detect_compiler.cpp @@ -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 + +struct A { int field; }; + +int main() { + (void)boost::pfr::get_name<0, A>(); // Must be a compile time error +} + + diff --git a/test/core_name/compile-fail/fields_names_misconfigured_compiler.cpp b/test/core_name/compile-fail/fields_names_misconfigured_compiler.cpp new file mode 100644 index 00000000..bdbb5ca5 --- /dev/null +++ b/test/core_name/compile-fail/fields_names_misconfigured_compiler.cpp @@ -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 + +struct A { int field; }; + +int main() { + (void)boost::pfr::get_name<0, A>(); // Must be a compile time error +} + + diff --git a/test/core_name/print_name.cpp b/test/core_name/print_name.cpp new file mode 100644 index 00000000..d447cc13 --- /dev/null +++ b/test/core_name/print_name.cpp @@ -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 + +// 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 + +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; +} +