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();