diff --git a/include/boost/pfr/detail/fields_count.hpp b/include/boost/pfr/detail/fields_count.hpp index df1083f..446c171 100644 --- a/include/boost/pfr/detail/fields_count.hpp +++ b/include/boost/pfr/detail/fields_count.hpp @@ -39,7 +39,7 @@ struct ubiq_constructor_except { template constexpr operator std::enable_if_t::value, Type&> () const noexcept; // Undefined }; -///////////////////// Hand-made is_aggregate_initializable_n trait: +///////////////////// Hand-made is_aggregate_initializable_n trait // `std::is_constructible>` consumes a lot of time, so we made a separate lazy trait for it. template struct is_single_field_and_aggregate_initializable: std::false_type {}; @@ -47,6 +47,9 @@ template struct is_single_field_and_aggregate_initializable<1, T>: std bool, !std::is_constructible>::value > {}; +// Hand-made is_aggregate trait: +// Aggregates could be constructed from `decltype(ubiq_constructor{I})...` but report that there's no constructor from `decltype(ubiq_constructor{I})...` +// Special case for N == 1: `std::is_constructible` returns true if N == 1 and T is copy/move constructible. template struct is_aggregate_initializable_n { template @@ -59,51 +62,81 @@ struct is_aggregate_initializable_n { static constexpr bool value = std::is_empty::value || std::is_fundamental::value - || ( - is_not_constructible_n(std::make_index_sequence{}) /*&& N*/) + || is_not_constructible_n(std::make_index_sequence{}) ; }; -///////////////////// Methods for detecting max parameters for construction of T - +///////////////////// Helper for SFINAE on fields count template constexpr auto enable_if_constructible_helper(std::index_sequence) noexcept -> typename std::add_pointer::type; +template (std::make_index_sequence()) ) > +using enable_if_constructible_helper_t = std::size_t; + +///////////////////// Non greedy fields count search. Templates instantiation depth is log(sizeof(T)), templates instantiation count is log(sizeof(T)). template -constexpr void detect_fields_count(std::size_t& count, size_t_, size_t_, long) noexcept { - // Hand-made is_aggregate trait: - // Aggregates could be constructed from `decltype(ubiq_constructor{I})...` but report that there's no constructor from `decltype(ubiq_constructor{I})...` - // Special case for N == 1: `std::is_constructible` returns true if N == 1 and T is copy/move constructible. - static_assert( - is_aggregate_initializable_n::value, - "Types with user specified constructors (non-aggregate initializable types) are not supported." - ); - - static_assert( // TODO: implement a greedy serach for such types - N != 0 || std::is_empty::value || std::is_fundamental::value, - "There's no way to detect fields count for a non default initilizable type." - ); - - count = N; +constexpr std::size_t detect_fields_count(size_t_, size_t_, long) noexcept { + return N; } template -constexpr void detect_fields_count(std::size_t& count, size_t_, size_t_, int) noexcept; +constexpr std::size_t detect_fields_count(size_t_, size_t_, int) noexcept; template -constexpr auto detect_fields_count(std::size_t& count, size_t_, size_t_, long) noexcept - -> decltype( enable_if_constructible_helper(std::make_index_sequence()) ) +constexpr auto detect_fields_count(size_t_, size_t_, long) noexcept + -> enable_if_constructible_helper_t { constexpr std::size_t next = Middle + (Middle - Begin + 1) / 2; - detect_fields_count(count, size_t_{}, size_t_{}, 1L); - return nullptr; + return detect_fields_count(size_t_{}, size_t_{}, 1L); } template -constexpr void detect_fields_count(std::size_t& count, size_t_, size_t_, int) noexcept { +constexpr std::size_t detect_fields_count(size_t_, size_t_, int) noexcept { constexpr std::size_t next = (Begin + Middle) / 2; - detect_fields_count(count, size_t_{}, size_t_{}, 1L); + return detect_fields_count(size_t_{}, size_t_{}, 1L); +} + +///////////////////// Greedy search. Templates instantiation depth is log(sizeof(T)), templates instantiation count is log(sizeof(T))*T in worst case. +template +constexpr auto detect_fields_count_greedy_remember(size_t_, long) noexcept + -> enable_if_constructible_helper_t +{ + return N; +} + +template +constexpr std::size_t detect_fields_count_greedy_remember(size_t_, int) noexcept { + return 0; +} + +template +constexpr std::size_t detect_fields_count_greedy(size_t_, size_t_) noexcept { + return detect_fields_count_greedy_remember(size_t_{}, 1L); +} + +template +constexpr std::size_t detect_fields_count_greedy(size_t_, size_t_) noexcept { + constexpr std::size_t middle = Begin + (Last - Begin) / 2; + constexpr std::size_t fields_count_big = detect_fields_count_greedy(size_t_{}, size_t_{}); + constexpr std::size_t fields_count_small = detect_fields_count_greedy(size_t_{}, size_t_{}); + return fields_count_big ? fields_count_big : fields_count_small; +} + +///////////////////// Choosing between greedy and non greedy search. +template +constexpr auto detect_fields_count_dispatch(size_t_, long) noexcept + -> decltype(sizeof(T{})) +{ + return detect_fields_count(size_t_<0>{}, size_t_{}, 1L); +} + +template +constexpr std::size_t detect_fields_count_dispatch(size_t_, int) noexcept { + // T is not default aggregate initialzable. It means that at least one of the members is not default constructible, + // so we have to check all the aggregate initializations for T up to N parameters and return the bigest succeeded + // (we can not use binary search for detecting fields count). + return detect_fields_count_greedy(size_t_<0>{}, size_t_{}); } ///////////////////// Returns non-flattened fields count @@ -135,10 +168,20 @@ constexpr std::size_t fields_count() noexcept { // ); //#endif - std::size_t res = 0u; - constexpr std::size_t next = (sizeof(T) * 8) / 2 + 1; // We multiply by 8 because we may have bitfields in T - detect_fields_count(res, size_t_<0>{}, size_t_{}, 1L); - return res; + constexpr std::size_t max_fields_count = (sizeof(T) * 8); // We multiply by 8 because we may have bitfields in T + constexpr std::size_t result = detect_fields_count_dispatch(size_t_{}, 1L); + + static_assert( + is_aggregate_initializable_n::value, + "Types with user specified constructors (non-aggregate initializable types) are not supported." + ); + + static_assert( + result != 0 || std::is_empty::value || std::is_fundamental::value, + "Something went wrong. Please report this issue to the github along with the structure you're reflecting." + ); + + return result; } }}} // namespace boost::pfr::detail diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 9cdac69..5e4471e 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -58,10 +58,10 @@ test-suite pfr [ run common/non_std_layout.cpp : : : $(CLASSIC_PREC_DEF) : precise_non_standard_layout ] [ run common/non_std_layout.cpp : : : $(LOOPHOLE_PREC_DEF) : precise_lh_non_standard_layout ] - [ compile-fail common/non_default_constructible.cpp : $(CLASSIC_FLAT_DEF) : flat_non_default_constructible ] - [ compile-fail common/non_default_constructible.cpp : $(LOOPHOLE_FLAT_DEF) : flat_lh_non_sdefault_constructible ] - [ compile-fail common/non_default_constructible.cpp : $(CLASSIC_PREC_DEF) : precise_non_default_constructible ] - [ compile-fail common/non_default_constructible.cpp : $(LOOPHOLE_PREC_DEF) : precise_lh_non_default_constructible ] + [ compile-fail common/non_default_constructible.cpp : $(CLASSIC_FLAT_DEF) : flat_non_default_constructible ] + [ compile-fail common/non_default_constructible.cpp : $(LOOPHOLE_FLAT_DEF) : flat_lh_non_sdefault_constructible ] + [ run common/non_default_constructible.cpp : : : $(CLASSIC_PREC_DEF) : precise_non_default_constructible ] + [ run common/non_default_constructible.cpp : : : $(LOOPHOLE_PREC_DEF) : precise_lh_non_default_constructible ] ##### Tuple sizes diff --git a/test/common/non_default_constructible.cpp b/test/common/non_default_constructible.cpp index 8d978d7..4475c21 100644 --- a/test/common/non_default_constructible.cpp +++ b/test/common/non_default_constructible.cpp @@ -13,7 +13,25 @@ struct S { X x0; X x1; int x2; X x3; }; int main() { #ifdef BOOST_PFR_TEST_PRECISE - return boost::pfr::tuple_size_v; + static_assert(boost::pfr::tuple_size_v == 4, ""); + + struct S5_0 { int x0; int x1; int x2; int x3; X x4; }; + static_assert(boost::pfr::tuple_size_v == 5, ""); + + struct S5_1 { X x0; int x1; int x2; int x3; int x4; }; + static_assert(boost::pfr::tuple_size_v == 5, ""); + + struct S5_2 { int x0; int x1; X x2; int x3; int x4; }; + static_assert(boost::pfr::tuple_size_v == 5, ""); + + struct S5_3 { int x0; int x1; X x2; int x3; X x4; }; + static_assert(boost::pfr::tuple_size_v == 5, ""); + + struct S5_4 { X x0; X x1; X x2; X x3; X x4; }; + static_assert(boost::pfr::tuple_size_v == 5, ""); + + struct S6 { X x0; X x1; X x2; X x3; X x4; X x5;}; + static_assert(boost::pfr::tuple_size_v == 6, ""); #elif defined(BOOST_PFR_TEST_FLAT) return boost::pfr::flat_tuple_size_v; #else