From 5b55480c651a2a7e64e17ed8867ccf242cd31918 Mon Sep 17 00:00:00 2001 From: bela Date: Thu, 30 Nov 2023 10:09:09 +0100 Subject: [PATCH 1/4] Addressing the limitation of get_name --- doc/pfr.qbk | 4 -- example/quick_examples.cpp | 5 --- include/boost/pfr/detail/fake_object.hpp | 17 ++++++--- test/core_name/run/fields_names.cpp | 48 ++++++++++++++++++++++++ 4 files changed, 60 insertions(+), 14 deletions(-) diff --git a/doc/pfr.qbk b/doc/pfr.qbk index 131b30d..f2e85db 100644 --- a/doc/pfr.qbk +++ b/doc/pfr.qbk @@ -490,10 +490,6 @@ 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` with non-internal linkage (with aggregats that could be used as `extern T t;`). -Do not use this functionality with anonymous structures, local structures -or a structure defined inside anonymous namespace as the behavior tends to be non-portable. - [h2 Configuration Macro] diff --git a/example/quick_examples.cpp b/example/quick_examples.cpp index 9d82a71..50a79c6 100644 --- a/example/quick_examples.cpp +++ b/example/quick_examples.cpp @@ -106,11 +106,7 @@ void test_examples() { //] } - // Disabled from testing since it's unportable -#if 0 { -// Keep in mind that it's unportable code -// You should move this structure somewhere outside of function scope //[pfr_quick_examples_get_name // Get name of field by index @@ -123,7 +119,6 @@ void test_examples() { << boost::pfr::get_name<1, sample>(); // Outputs: f1 f2 //] } -#endif #if BOOST_PFR_USE_CPP17 || BOOST_PFR_USE_LOOPHOLE { diff --git a/include/boost/pfr/detail/fake_object.hpp b/include/boost/pfr/detail/fake_object.hpp index 85499dd..54e0f75 100644 --- a/include/boost/pfr/detail/fake_object.hpp +++ b/include/boost/pfr/detail/fake_object.hpp @@ -16,18 +16,25 @@ namespace boost { namespace pfr { namespace detail { -// This variable serves as a compile-time assert. If you see any error here, then -// you're probably using `boost::pfr::get_name()` or `boost::pfr::names_as_array()` with a non-external linkage type. +// This class has external linkage while T has not sure. template -extern const T passed_type_has_no_external_linkage; +struct wrapper { + const T value; +}; + +// This variable servers as a link-time assert. +// If linker requires it, then `fake_object()` is used at runtime. +template +extern const wrapper report_if_you_see_link_error_with_this_object; // For returning non default constructible types, it's exclusively used in member name retrieval. // // Neither std::declval nor boost::pfr::detail::unsafe_declval are usable there. -// Limitation - T should have external linkage. +// This takes advantage of C++20 features, while boost::pfr::detail::unsafe_declval works +// with the former standards. template constexpr const T& fake_object() noexcept { - return passed_type_has_no_external_linkage; + return report_if_you_see_link_error_with_this_object.value; } }}} // namespace boost::pfr::detail diff --git a/test/core_name/run/fields_names.cpp b/test/core_name/run/fields_names.cpp index ca7acbd..7fabee8 100644 --- a/test/core_name/run/fields_names.cpp +++ b/test/core_name/run/fields_names.cpp @@ -34,6 +34,19 @@ struct A { struct empty {}; +namespace { + struct inside_unnamed_ns { + int hidden; + }; +} + +struct { + int unnamed_first; + float unnamed_second +} unnamed{}; + +typedef typename std::remove_reference::type unnamed_t; + void test_get_name_by_id() { BOOST_TEST_EQ( ((boost::pfr::get_name<0, Aggregate>())), "member1"); BOOST_TEST_EQ( ((boost::pfr::get_name<1, Aggregate>())), "this_is_a_name"); @@ -44,6 +57,19 @@ void test_get_name_by_id() { BOOST_TEST_EQ( ((boost::pfr::get_name<1, A>())), "second"); } +void test_get_name_by_id_without_linkage() { + struct function_local { + int val; + }; + + BOOST_TEST_EQ( ((boost::pfr::get_name<0, unnamed_t>())), "unnamed_first"); + BOOST_TEST_EQ( ((boost::pfr::get_name<1, unnamed_t>())), "unnamed_second"); + + BOOST_TEST_EQ( ((boost::pfr::get_name<0, inside_unnamed_ns>())), "hidden"); + + BOOST_TEST_EQ( ((boost::pfr::get_name<0, function_local>())), "val"); +} + void test_get_name_by_type() { // FIXME: implement this // using char_ref = std::reference_wrapper; @@ -52,6 +78,13 @@ void test_get_name_by_type() { // BOOST_TEST_EQ( ((boost::pfr::get_name())), "c"); } +void test_get_name_by_type_without_linkage() { +// FIXME: implement this +// BOOST_TEST_EQ( ((boost::pfr::get_name())), "unnamed_first"); +// BOOST_TEST_EQ( ((boost::pfr::get_name())), "unnamed_second"); +// BOOST_TEST_EQ( ((boost::pfr::get_name())), "hidden"); +} + void test_names_as_array() { const auto expected = std::array{ "member1", @@ -66,6 +99,18 @@ void test_names_as_array() { } } +void test_names_as_array_without_linkage() { + const auto expected = std::array{ + "unnamed_first", + "unnamed_second" + }; + const auto value = boost::pfr::names_as_array(); + BOOST_TEST_EQ(expected.size(), value.size()); + for (std::size_t i=0;i(); BOOST_TEST_EQ(value.size(), 0); @@ -76,8 +121,11 @@ void test_names_as_array_for_empty() { int main() { testing::test_get_name_by_id(); + testing::test_get_name_by_id_without_linkage(); testing::test_get_name_by_type(); + testing::test_get_name_by_type_without_linkage(); testing::test_names_as_array(); + testing::test_names_as_array_without_linkage(); testing::test_names_as_array_for_empty(); return boost::report_errors(); From b674d728bedb250581231667f6c91f8cdf12c1e3 Mon Sep 17 00:00:00 2001 From: bela Date: Thu, 30 Nov 2023 10:14:01 +0100 Subject: [PATCH 2/4] revert field_name and SimpleAggregate comment --- doc/pfr.qbk | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/pfr.qbk b/doc/pfr.qbk index f2e85db..bb174fd 100644 --- a/doc/pfr.qbk +++ b/doc/pfr.qbk @@ -490,6 +490,8 @@ 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 only `SimpleAggregate` types. + [h2 Configuration Macro] From 8a0447b4868d997439cfdd375ad0a82904c9b72f Mon Sep 17 00:00:00 2001 From: bela Date: Thu, 30 Nov 2023 11:30:20 +0100 Subject: [PATCH 3/4] Fix missing ; --- test/core_name/run/fields_names.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/core_name/run/fields_names.cpp b/test/core_name/run/fields_names.cpp index 7fabee8..689d023 100644 --- a/test/core_name/run/fields_names.cpp +++ b/test/core_name/run/fields_names.cpp @@ -42,7 +42,7 @@ namespace { struct { int unnamed_first; - float unnamed_second + float unnamed_second; } unnamed{}; typedef typename std::remove_reference::type unnamed_t; From 16a97bb1f52f13312454bc7dacf7238e5c8e256b Mon Sep 17 00:00:00 2001 From: bela Date: Thu, 30 Nov 2023 11:41:58 +0100 Subject: [PATCH 4/4] Fix sample compilation --- example/quick_examples.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/example/quick_examples.cpp b/example/quick_examples.cpp index 50a79c6..639342b 100644 --- a/example/quick_examples.cpp +++ b/example/quick_examples.cpp @@ -106,6 +106,7 @@ void test_examples() { //] } +#if BOOST_PFR_CORE_NAME_ENABLED { //[pfr_quick_examples_get_name // Get name of field by index @@ -119,6 +120,7 @@ void test_examples() { << boost::pfr::get_name<1, sample>(); // Outputs: f1 f2 //] } +#endif #if BOOST_PFR_USE_CPP17 || BOOST_PFR_USE_LOOPHOLE {