diff --git a/doc/pfr.qbk b/doc/pfr.qbk index af2841b..74ce87c 100644 --- a/doc/pfr.qbk +++ b/doc/pfr.qbk @@ -501,6 +501,7 @@ Boost.PFRs extraction of field name works with only `SimpleAggregate` types. 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: [table:linkmacro Macros [[Macro name] [Effect]] + [[*BOOST_PFR_USE_CPP26*] [Define to `1` if you wish to override Boost.PFR choice and use C++26 variadic structured bindings for reflection. Define to `0` to override Boost.PFR choice and disable C++26 variadic structured bindings usage.]] [[*BOOST_PFR_USE_CPP17*] [Define to `1` if you wish to override Boost.PFR choice and use C++17 structured bindings for reflection. Define to `0` to override Boost.PFR choice and disable C++17 structured bindings usage.]] [[*BOOST_PFR_USE_LOOPHOLE*] [Define to `1` if you wish to override Boost.PFR choice and exploit [@http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#2118 CWG 2118] for reflection. Define to `0` to override Boost.PFR choice and disable CWG 2118 usage.]] [[*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. ]] @@ -593,16 +594,18 @@ clang++ -std=c++20 -fmodule-file=boost_pfr.pcm boost_pfr.pcm usage_sample.cpp Short description: # at compile-time: use aggregate initialization to detect fields count in user-provided structure - * [*BOOST_PFR_USE_CPP17 == 1]: + * [*BOOST_PFR_USE_CPP26 == 1]: + # at compile-time: structured bindings are used to decompose a type `T` to known variadic amount of fields + * [*BOOST_PFR_USE_CPP26 == 0 && BOOST_PFR_USE_CPP17 == 1]: # at compile-time: structured bindings are used to decompose a type `T` to known amount of fields - * [*BOOST_PFR_USE_CPP17 == 0 && BOOST_PFR_USE_LOOPHOLE == 1]: + * [*BOOST_PFR_USE_CPP26 == 0 && BOOST_PFR_USE_CPP17 == 0 && BOOST_PFR_USE_LOOPHOLE == 1]: # at compile-time: use aggregate initialization to detect fields count in user-provided structure # at compile-time: make a structure that is convertible to anything and remember types it has been converted to during aggregate initialization of user-provided structure # at compile-time: using knowledge from previous steps create a tuple with exactly the same layout as in user-provided structure # at compile-time: find offsets for each field in user-provided structure using the tuple from previous step # at run-time: get pointer to each field, knowing the structure address and each field offset # at run-time: a tuple of references to fields is returned => all the tuple methods are available for the structure - * [*BOOST_PFR_USE_CPP17 == 0 && BOOST_PFR_USE_LOOPHOLE == 0]: + * [*BOOST_PFR_USE_CPP26 == 0 && BOOST_PFR_USE_CPP17 == 0 && BOOST_PFR_USE_LOOPHOLE == 0]: # at compile-time: let `I` be is an index of current field, it equals 0 # at run-time: `T` is constructed and field `I` is aggregate initialized using a separate instance of structure that is convertible to anything [note Additional care is taken to make sure that all the information about `T` is available to the compiler and that operations on `T` have no side effects, so the compiler can optimize away the unnecessary temporary objects.] # at compile-time: `I += 1` diff --git a/include/boost/pfr/config.hpp b/include/boost/pfr/config.hpp index 4a88baa..7b35073 100644 --- a/include/boost/pfr/config.hpp +++ b/include/boost/pfr/config.hpp @@ -10,7 +10,9 @@ #if !defined(BOOST_USE_MODULES) && (__cplusplus >= 201402L || (defined(_MSC_VER) && defined(_MSVC_LANG) && _MSC_VER > 1900)) #include // to get non standard platform macro definitions (__GLIBCXX__ for example) -#elif defined(BOOST_USE_MODULES) +#endif + +#if defined(BOOST_USE_MODULES) || __cplusplus >= 202002L #include #endif @@ -51,6 +53,14 @@ # endif #endif +#ifndef BOOST_PFR_USE_CPP26 +#if __cpp_structured_bindings >= 202411L && __cpp_lib_forward_like >= 202207L +#define BOOST_PFR_USE_CPP26 1 +#else +#define BOOST_PFR_USE_CPP26 0 +#endif +#endif + #ifndef BOOST_PFR_USE_CPP17 # ifdef __cpp_structured_bindings # define BOOST_PFR_USE_CPP17 1 @@ -65,7 +75,7 @@ # endif #endif -#if (!BOOST_PFR_USE_CPP17 && !BOOST_PFR_USE_LOOPHOLE) +#if (!BOOST_PFR_USE_CPP26 && !BOOST_PFR_USE_CPP17 && !BOOST_PFR_USE_LOOPHOLE) # if (defined(_MSC_VER) && _MSC_VER < 1916) ///< in Visual Studio 2017 v15.9 PFR library with classic engine normally works # define BOOST_PFR_NOT_SUPPORTED 1 # endif diff --git a/include/boost/pfr/core.hpp b/include/boost/pfr/core.hpp index 6265f46..a7908f7 100644 --- a/include/boost/pfr/core.hpp +++ b/include/boost/pfr/core.hpp @@ -52,7 +52,12 @@ BOOST_PFR_BEGIN_MODULE_EXPORT /// \endcode template constexpr decltype(auto) get(const T& val) noexcept { - return detail::sequence_tuple::get( detail::tie_as_tuple(val) ); +#if BOOST_PFR_USE_CPP26 + const auto &[... members] = val; + return std::forward_like(members...[I]); +#else + return detail::sequence_tuple::get(detail::tie_as_tuple(val)); +#endif } /// \overload get @@ -62,7 +67,12 @@ constexpr decltype(auto) get(T& val , std::enable_if_t::value>* = nullptr #endif ) noexcept { +#if BOOST_PFR_USE_CPP26 + auto &[... members] = val; + return std::forward_like(members...[I]); +#else return detail::sequence_tuple::get( detail::tie_as_tuple(val) ); +#endif } #if !BOOST_PFR_USE_CPP17 @@ -78,7 +88,12 @@ constexpr auto get(T&, std::enable_if_t::value>* = nul /// \overload get template constexpr auto get(T&& val, std::enable_if_t< std::is_rvalue_reference::value>* = nullptr) noexcept { +#if BOOST_PFR_USE_CPP26 + auto &&[... members] = std::forward(val); + return std::move(members...[I]); +#else return std::move(detail::sequence_tuple::get( detail::tie_as_tuple(val) )); +#endif } @@ -147,10 +162,15 @@ using tuple_element_t = typename tuple_element::type; /// \endcode template constexpr auto structure_to_tuple(const T& val) { +#if BOOST_PFR_USE_CPP26 + const auto &[... members] = val; + return std::make_tuple(members...); +#else return detail::make_stdtuple_from_tietuple( detail::tie_as_tuple(val), detail::make_index_sequence< tuple_size_v >() ); +#endif } @@ -172,10 +192,15 @@ constexpr auto structure_to_tuple(const T& val) { /// \endcode template constexpr auto structure_tie(const T& val) noexcept { +#if BOOST_PFR_USE_CPP26 + const auto &[... members] = val; + return std::tie(std::forward_like(members)...); +#else return detail::make_conststdtiedtuple_from_tietuple( detail::tie_as_tuple(const_cast(val)), detail::make_index_sequence< tuple_size_v >() ); +#endif } @@ -186,10 +211,13 @@ constexpr auto structure_tie(T& val , std::enable_if_t::value>* = nullptr #endif ) noexcept { - return detail::make_stdtiedtuple_from_tietuple( - detail::tie_as_tuple(val), - detail::make_index_sequence< tuple_size_v >() - ); +#if BOOST_PFR_USE_CPP26 + auto &[... members] = val; + return std::tie(std::forward_like(members)...); +#else + return detail::make_stdtiedtuple_from_tietuple(detail::tie_as_tuple(val), + detail::make_index_sequence >()); +#endif } #if !BOOST_PFR_USE_CPP17 diff --git a/include/boost/pfr/detail/core.hpp b/include/boost/pfr/detail/core.hpp index e54bd1e..9c97b03 100644 --- a/include/boost/pfr/detail/core.hpp +++ b/include/boost/pfr/detail/core.hpp @@ -13,7 +13,9 @@ // `boost::pfr::detail::for_each_field_dispatcher` functions. // // The whole PFR library is build on top of those two functions. -#if BOOST_PFR_USE_CPP17 +#if BOOST_PFR_USE_CPP26 +#include +#elif BOOST_PFR_USE_CPP17 # include #elif BOOST_PFR_USE_LOOPHOLE # include diff --git a/include/boost/pfr/detail/core26.hpp b/include/boost/pfr/detail/core26.hpp new file mode 100644 index 0000000..0412dc0 --- /dev/null +++ b/include/boost/pfr/detail/core26.hpp @@ -0,0 +1,29 @@ +// Copyright (c) 2016-2025 Antony Polukhin +// Copyright (c) 2025 Jean-Michaƫl Celerier +// +// 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) + +// boost-no-inspect + +#ifndef BOOST_PFR_DETAIL_CORE26_HPP +#define BOOST_PFR_DETAIL_CORE26_HPP +#pragma once + +#include + +namespace boost::pfr::detail { + +template +constexpr auto tie_as_tuple(T &val) noexcept +{ + static_assert(!std::is_union::value, + "====================> Boost.PFR: For safety reasons it is forbidden to reflect " + "unions. See `Reflection of unions` section in the docs for more info."); + auto &&[... members] = std::forward(val); + return sequence_tuple::tuple...>{members...}; +} + +} // namespace boost::pfr::detail + +#endif diff --git a/include/boost/pfr/detail/fields_count.hpp b/include/boost/pfr/detail/fields_count.hpp index 878d64f..e49b4cd 100644 --- a/include/boost/pfr/detail/fields_count.hpp +++ b/include/boost/pfr/detail/fields_count.hpp @@ -207,6 +207,7 @@ using is_one_element_range = std::integral_constant; using multi_element_range = std::false_type; using one_element_range = std::true_type; +#if !BOOST_PFR_USE_CPP26 ///////////////////// Fields count next expected compiler limitation constexpr std::size_t fields_count_compiler_limitation_next(std::size_t n) noexcept { #if defined(_MSC_VER) && (_MSC_VER <= 1920) @@ -328,8 +329,23 @@ constexpr std::size_t fields_count_lower_bound_unbounded(int, size_t_<0>) noexce ); return detail::fields_count_lower_bound_unbounded(1L, size_t_{}); } +#endif ///////////////////// Choosing between array size, unbounded binary search, and linear search followed by unbounded binary search. +#if BOOST_PFR_USE_CPP26 +template +constexpr auto fields_count_dispatch_impl(const T &t) noexcept +{ + const auto &[... elts] = t; + return std::integral_constant{}; +} + +template +constexpr auto fields_count_dispatch() noexcept +{ + return decltype(fields_count_dispatch_impl(std::declval()))::value; +} +#else template constexpr auto fields_count_dispatch(long, long, std::false_type /*are_preconditions_met*/) noexcept { return 0; @@ -360,6 +376,7 @@ constexpr std::size_t fields_count_dispatch(int, int, std::true_type /*are_preco constexpr std::size_t last = detail::fields_count_upper_bound(1L, 1L); return detail::fields_count_binary_search(detail::is_one_element_range{}, 1L); } +#endif ///////////////////// Returns fields count template @@ -425,9 +442,12 @@ constexpr std::size_t fields_count() noexcept { constexpr bool no_errors = type_is_complete && type_is_not_a_reference && type_fields_are_move_constructible && type_is_not_polymorphic && type_is_aggregate; - - constexpr std::size_t result = detail::fields_count_dispatch(1L, 1L, std::integral_constant{}); - +#if BOOST_PFR_USE_CPP26 + constexpr std::size_t result = detail::fields_count_dispatch(); +#else + constexpr std::size_t result + = detail::fields_count_dispatch(1L, 1L, std::integral_constant{}); +#endif detail::assert_first_not_base(1L); #ifndef __cpp_lib_is_aggregate diff --git a/include/boost/pfr/detail/for_each_field.hpp b/include/boost/pfr/detail/for_each_field.hpp index 979226e..667ef46 100644 --- a/include/boost/pfr/detail/for_each_field.hpp +++ b/include/boost/pfr/detail/for_each_field.hpp @@ -16,12 +16,27 @@ #if !defined(BOOST_PFR_INTERFACE_UNIT) #include // metaprogramming stuff +#include // forward_like #endif namespace boost { namespace pfr { namespace detail { template constexpr void for_each_field(T&& value, F&& func) { +#if BOOST_PFR_USE_CPP26 + if constexpr (std::is_aggregate_v || std::is_bounded_array_v>) { + auto &&[... members] = value; + ::boost::pfr::detail::for_each_field_impl(value, + std::forward(func), + std::make_index_sequence{}, + std::is_rvalue_reference{}); + } else { + ::boost::pfr::detail::for_each_field_impl(value, + std::forward(func), + std::make_index_sequence<1>{}, + std::is_rvalue_reference{}); + } +#else constexpr std::size_t fields_count_val = boost::pfr::detail::fields_count>(); ::boost::pfr::detail::for_each_field_dispatcher( @@ -40,6 +55,7 @@ constexpr void for_each_field(T&& value, F&& func) { }, detail::make_index_sequence{} ); +#endif } }}} // namespace boost::pfr::detail diff --git a/include/boost/pfr/detail/for_each_field_impl.hpp b/include/boost/pfr/detail/for_each_field_impl.hpp index 24bd715..7f68f72 100644 --- a/include/boost/pfr/detail/for_each_field_impl.hpp +++ b/include/boost/pfr/detail/for_each_field_impl.hpp @@ -21,6 +21,43 @@ namespace boost { namespace pfr { namespace detail { template using size_t_ = std::integral_constant; +#if BOOST_PFR_USE_CPP26 +template()(std::declval(), I{}))> +constexpr void for_each_field_impl_apply(T &&v, F &&f, I i, long) +{ + std::forward(f)(std::forward(v), i); +} + +template +constexpr void for_each_field_impl_apply(T &&v, F &&f, I /*i*/, int) +{ + std::forward(f)(std::forward(v)); +} + +template +constexpr void for_each_field_impl(T &t, F &&f, std::index_sequence, auto move_values) +{ + if constexpr (std::is_aggregate_v || std::is_bounded_array_v>) { + auto &&[... members] = t; + if constexpr (move_values) + (detail::for_each_field_impl_apply(std::move(members...[I]), + std::forward(f), + size_t_{}, + 1L), + ...); + else + (detail::for_each_field_impl_apply( + members... [I], std::forward(f), size_t_ {}, 1L), + ...); + } else { + if constexpr (move_values) + (detail::for_each_field_impl_apply(std::move(t), std::forward(f), size_t_{}, 1L), + ...); + else + (detail::for_each_field_impl_apply(t, std::forward(f), size_t_{}, 1L), ...); + } +} +#else template ()(std::declval(), I{}))> constexpr void for_each_field_impl_apply(T&& v, F&& f, I i, long) { std::forward(f)(std::forward(v), i); @@ -61,8 +98,7 @@ constexpr void for_each_field_impl(T& t, F&& f, std::index_sequence, std:: (detail::for_each_field_impl_apply(sequence_tuple::get(std::move(t)), std::forward(f), size_t_{}, 1L), ...); } #endif - +#endif }}} // namespace boost::pfr::detail - #endif // BOOST_PFR_DETAIL_FOR_EACH_FIELD_IMPL_HPP diff --git a/test/config/print_config.cpp b/test/config/print_config.cpp index 9d267ed..f7cd0ee 100644 --- a/test/config/print_config.cpp +++ b/test/config/print_config.cpp @@ -14,6 +14,7 @@ int main() { std::cout << "Platform info:" << '\n' << "BOOST_PFR_USE_CPP17 == " << BOOST_PFR_USE_CPP17 << '\n' + << "BOOST_PFR_USE_CPP26 == " << BOOST_PFR_USE_CPP26 << '\n' << "BOOST_PFR_USE_LOOPHOLE == " << BOOST_PFR_USE_LOOPHOLE << '\n' << "BOOST_PFR_USE_STD_MAKE_INTEGRAL_SEQUENCE == " << BOOST_PFR_USE_STD_MAKE_INTEGRAL_SEQUENCE << '\n' << "BOOST_PFR_HAS_GUARANTEED_COPY_ELISION == " << BOOST_PFR_HAS_GUARANTEED_COPY_ELISION << '\n' diff --git a/test/core/Jamfile.v2 b/test/core/Jamfile.v2 index 855ff8c..e77b860 100644 --- a/test/core/Jamfile.v2 +++ b/test/core/Jamfile.v2 @@ -23,7 +23,7 @@ project [ requires cxx14_constexpr ] ; -########## BEGIN of helpers to detect Loophole trick support +########## BEGIN of helpers to detect Loophole and variadic structured binding support actions mp_simple_run_action { @@ -42,6 +42,9 @@ rule mp-run-simple ( sources + : args * : input-files * : requirements * : targe mp-run-simple loophole_detection.cpp : : : : compiler_supports_loophole ; explicit compiler_supports_loophole ; +mp-run-simple variadic_structured_binding_detection.cpp : : : : compiler_supports_vsb ; +explicit compiler_supports_vsb ; + ########## END of helpers to detect Loophole trick support @@ -49,10 +52,14 @@ local DISABLE_ON_MSVC = ; #msvc:no ; local REQUIRE_LOOPHOLE = [ check-target-builds ../core//compiler_supports_loophole : : no ] ; +local REQUIRE_VSB = + [ check-target-builds ../core//compiler_supports_vsb : : no ] +; -local STRUCTURED_BINDING_ENGINE = BOOST_PFR_USE_LOOPHOLE=0 BOOST_PFR_USE_CPP17=1 [ requires cxx17_structured_bindings ] ; -local LOOPHOLE_ENGINE = BOOST_PFR_USE_LOOPHOLE=1 BOOST_PFR_USE_CPP17=0 $(REQUIRE_LOOPHOLE) ; -local CLASSIC_ENGINE = BOOST_PFR_USE_LOOPHOLE=0 BOOST_PFR_USE_CPP17=0 $(DISABLE_ON_MSVC) ; +local VARIADIC_STRUCTURED_BINDING_ENGINE = BOOST_PFR_USE_LOOPHOLE=0 BOOST_PFR_USE_CPP17=0 BOOST_PFR_USE_CPP26=1 $(REQUIRE_VSB) ; +local STRUCTURED_BINDING_ENGINE = BOOST_PFR_USE_LOOPHOLE=0 BOOST_PFR_USE_CPP17=1 BOOST_PFR_USE_CPP26=0 [ requires cxx17_structured_bindings ] ; +local LOOPHOLE_ENGINE = BOOST_PFR_USE_LOOPHOLE=1 BOOST_PFR_USE_CPP17=0 BOOST_PFR_USE_CPP26=0 $(REQUIRE_LOOPHOLE) ; +local CLASSIC_ENGINE = BOOST_PFR_USE_LOOPHOLE=0 BOOST_PFR_USE_CPP17=0 BOOST_PFR_USE_CPP26=0 $(DISABLE_ON_MSVC) ; test-suite pfr_tests : @@ -109,6 +116,7 @@ local BLACKLIST_TESTS_FOR_CLASSIC = for local source_file in [ glob ./run/*.cpp ] [ glob ../../example/*.cpp ] { local target_name = $(source_file[1]:B) ; + pfr_tests += [ run $(source_file) : : : $(VARIADIC_STRUCTURED_BINDING_ENGINE) : $(target_name)_vsb ] ; pfr_tests += [ run $(source_file) : : : $(STRUCTURED_BINDING_ENGINE) : $(target_name)_sb ] ; if ! $(target_name) in $(BLACKLIST_TESTS_FOR_LOOPHOLE) diff --git a/test/core/run/core17_generated.cpp b/test/core/run/core17_generated.cpp index 92ca910..6cffeff 100644 --- a/test/core/run/core17_generated.cpp +++ b/test/core/run/core17_generated.cpp @@ -24,7 +24,7 @@ struct A { }; int main() { -#if BOOST_PFR_USE_CPP17 && !defined(BOOST_USE_MODULES) // TODO: fix for BOOST_USE_MODULES +#if BOOST_PFR_USE_CPP17 && !BOOST_PFR_USE_CPP26 && !defined(BOOST_USE_MODULES) // TODO: fix for BOOST_USE_MODULES const volatile int cv_value = 0; volatile int v_value = 0; const int c_value = 0; diff --git a/test/core/variadic_structured_binding_detection.cpp b/test/core/variadic_structured_binding_detection.cpp new file mode 100644 index 0000000..816516e --- /dev/null +++ b/test/core/variadic_structured_binding_detection.cpp @@ -0,0 +1,23 @@ +// Copyright (c) 2020-2025 Antony Polukhin +// +// 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) + +// Detection of variadic structured binding support + +#include + +struct MyPair { + int first; + int second; +}; + +template +auto test() { + const auto& [...x] = T{}; + return x...[0]; +} + +int main() { + return ::test(); +}