fields_count.hpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. // Copyright (c) 2016-2025 Antony Polukhin
  2. //
  3. // Distributed under the Boost Software License, Version 1.0. (See accompanying
  4. // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  5. #ifndef BOOST_PFR_DETAIL_FIELDS_COUNT_HPP
  6. #define BOOST_PFR_DETAIL_FIELDS_COUNT_HPP
  7. #pragma once
  8. #include <boost/pfr/detail/config.hpp>
  9. #include <boost/pfr/detail/make_integer_sequence.hpp>
  10. #include <boost/pfr/detail/size_t_.hpp>
  11. #include <boost/pfr/detail/unsafe_declval.hpp>
  12. #if !defined(BOOST_PFR_INTERFACE_UNIT)
  13. #include <limits>
  14. #include <type_traits>
  15. #include <utility> // metaprogramming stuff
  16. #endif
  17. #ifdef __clang__
  18. # pragma clang diagnostic push
  19. # pragma clang diagnostic ignored "-Wmissing-braces"
  20. # pragma clang diagnostic ignored "-Wundefined-inline"
  21. # pragma clang diagnostic ignored "-Wundefined-internal"
  22. # pragma clang diagnostic ignored "-Wmissing-field-initializers"
  23. #endif
  24. namespace boost { namespace pfr { namespace detail {
  25. ///////////////////// min without including <algorithm>
  26. constexpr std::size_t min_of_size_t(std::size_t a, std::size_t b) noexcept {
  27. return b < a ? b : a;
  28. }
  29. ///////////////////// Structure that can be converted to reference to anything
  30. struct ubiq_lref_constructor {
  31. std::size_t ignore;
  32. template <class Type> constexpr operator Type&() const && noexcept { // tweak for template_unconstrained.cpp like cases
  33. return detail::unsafe_declval<Type&>();
  34. }
  35. template <class Type> constexpr operator Type&() const & noexcept { // tweak for optional_chrono.cpp like cases
  36. return detail::unsafe_declval<Type&>();
  37. }
  38. };
  39. ///////////////////// Structure that can be converted to rvalue reference to anything
  40. struct ubiq_rref_constructor {
  41. std::size_t ignore;
  42. template <class Type> /*constexpr*/ operator Type() const && noexcept { // Allows initialization of rvalue reference fields and move-only types
  43. return detail::unsafe_declval<Type>();
  44. }
  45. };
  46. ///////////////////// Hand-made is_complete<T> trait
  47. template <typename T, typename = void>
  48. struct is_complete : std::false_type
  49. {};
  50. template <typename T>
  51. struct is_complete<T, decltype(void(sizeof(T)))> : std::integral_constant<bool, true>
  52. {};
  53. #ifndef __cpp_lib_is_aggregate
  54. ///////////////////// Hand-made is_aggregate_initializable_n<T> trait
  55. // Structure that can be converted to reference to anything except reference to T
  56. template <class T, bool IsCopyConstructible>
  57. struct ubiq_constructor_except {
  58. std::size_t ignore;
  59. template <class Type> constexpr operator std::enable_if_t<!std::is_same<T, Type>::value, Type&> () const noexcept; // Undefined
  60. };
  61. template <class T>
  62. struct ubiq_constructor_except<T, false> {
  63. std::size_t ignore;
  64. template <class Type> constexpr operator std::enable_if_t<!std::is_same<T, Type>::value, Type&&> () const noexcept; // Undefined
  65. };
  66. // `std::is_constructible<T, ubiq_constructor_except<T>>` consumes a lot of time, so we made a separate lazy trait for it.
  67. template <std::size_t N, class T> struct is_single_field_and_aggregate_initializable: std::false_type {};
  68. template <class T> struct is_single_field_and_aggregate_initializable<1, T>: std::integral_constant<
  69. bool, !std::is_constructible<T, ubiq_constructor_except<T, std::is_copy_constructible<T>::value>>::value
  70. > {};
  71. // Hand-made is_aggregate<T> trait:
  72. // Before C++20 aggregates could be constructed from `decltype(ubiq_?ref_constructor{I})...` but type traits report that
  73. // there's no constructor from `decltype(ubiq_?ref_constructor{I})...`
  74. // Special case for N == 1: `std::is_constructible<T, ubiq_?ref_constructor>` returns true if N == 1 and T is copy/move constructible.
  75. template <class T, std::size_t N, class /*Enable*/ = void>
  76. struct is_aggregate_initializable_n {
  77. static constexpr bool value =
  78. std::is_empty<T>::value
  79. || std::is_array<T>::value
  80. ;
  81. };
  82. template <class T, std::size_t N>
  83. struct is_aggregate_initializable_n<T, N, std::enable_if_t<std::is_class<T>::value && !std::is_empty<T>::value>> {
  84. template <std::size_t ...I>
  85. static constexpr bool is_not_constructible_n(std::index_sequence<I...>) noexcept {
  86. return (!std::is_constructible<T, decltype(ubiq_lref_constructor{I})...>::value && !std::is_constructible<T, decltype(ubiq_rref_constructor{I})...>::value)
  87. || is_single_field_and_aggregate_initializable<N, T>::value
  88. ;
  89. }
  90. static constexpr bool value = is_not_constructible_n(detail::make_index_sequence<N>{});
  91. };
  92. #endif // #ifndef __cpp_lib_is_aggregate
  93. ///////////////////// Detect aggregates with inheritance
  94. template <class Derived, class U>
  95. constexpr bool static_assert_non_inherited() noexcept {
  96. static_assert(
  97. !std::is_base_of<U, Derived>::value,
  98. "====================> Boost.PFR: Boost.PFR: Inherited types are not supported."
  99. );
  100. return true;
  101. }
  102. template <class Derived>
  103. struct ubiq_lref_base_asserting {
  104. template <class Type> constexpr operator Type&() const && // tweak for template_unconstrained.cpp like cases
  105. noexcept(detail::static_assert_non_inherited<Derived, Type>()) // force the computation of assert function
  106. {
  107. return detail::unsafe_declval<Type&>();
  108. }
  109. template <class Type> constexpr operator Type&() const & // tweak for optional_chrono.cpp like cases
  110. noexcept(detail::static_assert_non_inherited<Derived, Type>()) // force the computation of assert function
  111. {
  112. return detail::unsafe_declval<Type&>();
  113. }
  114. };
  115. template <class Derived>
  116. struct ubiq_rref_base_asserting {
  117. template <class Type> /*constexpr*/ operator Type() const && // Allows initialization of rvalue reference fields and move-only types
  118. noexcept(detail::static_assert_non_inherited<Derived, Type>()) // force the computation of assert function
  119. {
  120. return detail::unsafe_declval<Type>();
  121. }
  122. };
  123. template <class T, std::size_t I0, std::size_t... I, class /*Enable*/ = std::enable_if_t<std::is_copy_constructible<T>::value>>
  124. constexpr auto assert_first_not_base(std::index_sequence<I0, I...>) noexcept
  125. -> std::add_pointer_t<decltype(T{ ubiq_lref_base_asserting<T>{}, ubiq_lref_constructor{I}... })>
  126. {
  127. return nullptr;
  128. }
  129. template <class T, std::size_t I0, std::size_t... I, class /*Enable*/ = std::enable_if_t<!std::is_copy_constructible<T>::value>>
  130. constexpr auto assert_first_not_base(std::index_sequence<I0, I...>) noexcept
  131. -> std::add_pointer_t<decltype(T{ ubiq_rref_base_asserting<T>{}, ubiq_rref_constructor{I}... })>
  132. {
  133. return nullptr;
  134. }
  135. template <class T>
  136. constexpr void* assert_first_not_base(std::index_sequence<>) noexcept
  137. {
  138. return nullptr;
  139. }
  140. template <class T, std::size_t N>
  141. constexpr void assert_first_not_base(int) noexcept {}
  142. template <class T, std::size_t N>
  143. constexpr auto assert_first_not_base(long) noexcept
  144. -> std::enable_if_t<std::is_class<T>::value>
  145. {
  146. detail::assert_first_not_base<T>(detail::make_index_sequence<N>{});
  147. }
  148. ///////////////////// Helpers for initializable detection
  149. // Note that these take O(N) compile time and memory!
  150. template <class T, std::size_t... I, class /*Enable*/ = std::enable_if_t<std::is_copy_constructible<T>::value>>
  151. constexpr auto enable_if_initializable_helper(std::index_sequence<I...>) noexcept
  152. -> std::add_pointer_t<decltype(T{ubiq_lref_constructor{I}...})>;
  153. template <class T, std::size_t... I, class /*Enable*/ = std::enable_if_t<!std::is_copy_constructible<T>::value>>
  154. constexpr auto enable_if_initializable_helper(std::index_sequence<I...>) noexcept
  155. -> std::add_pointer_t<decltype(T{ubiq_rref_constructor{I}...})>;
  156. template <class T, std::size_t N, class U = std::size_t, class /*Enable*/ = decltype(detail::enable_if_initializable_helper<T>(detail::make_index_sequence<N>()))>
  157. using enable_if_initializable_helper_t = U;
  158. template <class T, std::size_t N>
  159. constexpr auto is_initializable(long) noexcept
  160. -> detail::enable_if_initializable_helper_t<T, N, bool>
  161. {
  162. return true;
  163. }
  164. template <class T, std::size_t N>
  165. constexpr bool is_initializable(int) noexcept {
  166. return false;
  167. }
  168. ///////////////////// Helpers for range size detection
  169. template <std::size_t Begin, std::size_t Last>
  170. using is_one_element_range = std::integral_constant<bool, Begin == Last>;
  171. using multi_element_range = std::false_type;
  172. using one_element_range = std::true_type;
  173. #if !BOOST_PFR_USE_CPP26
  174. ///////////////////// Fields count next expected compiler limitation
  175. constexpr std::size_t fields_count_compiler_limitation_next(std::size_t n) noexcept {
  176. #if defined(_MSC_VER) && (_MSC_VER <= 1920)
  177. if (n < 1024)
  178. return 1024;
  179. #else
  180. static_cast<void>(n);
  181. #endif
  182. return (std::numeric_limits<std::size_t>::max)();
  183. }
  184. ///////////////////// Fields count upper bound based on sizeof(T)
  185. template <class T>
  186. constexpr std::size_t fields_count_upper_bound_loose() noexcept {
  187. return sizeof(T) * std::numeric_limits<unsigned char>::digits + 1 /* +1 for "Arrays of Length Zero" extension */;
  188. }
  189. ///////////////////// Fields count binary search.
  190. // Template instantiation: depth is O(log(result)), count is O(log(result)), cost is O(result * log(result)).
  191. template <class T, std::size_t Begin, std::size_t Last>
  192. constexpr std::size_t fields_count_binary_search(detail::one_element_range, long) noexcept {
  193. static_assert(
  194. Begin == Last,
  195. "====================> Boost.PFR: Internal logic error."
  196. );
  197. return Begin;
  198. }
  199. template <class T, std::size_t Begin, std::size_t Last>
  200. constexpr std::size_t fields_count_binary_search(detail::multi_element_range, int) noexcept;
  201. template <class T, std::size_t Begin, std::size_t Last>
  202. constexpr auto fields_count_binary_search(detail::multi_element_range, long) noexcept
  203. -> detail::enable_if_initializable_helper_t<T, (Begin + Last + 1) / 2>
  204. {
  205. constexpr std::size_t next_v = (Begin + Last + 1) / 2;
  206. return detail::fields_count_binary_search<T, next_v, Last>(detail::is_one_element_range<next_v, Last>{}, 1L);
  207. }
  208. template <class T, std::size_t Begin, std::size_t Last>
  209. constexpr std::size_t fields_count_binary_search(detail::multi_element_range, int) noexcept {
  210. constexpr std::size_t next_v = (Begin + Last + 1) / 2 - 1;
  211. return detail::fields_count_binary_search<T, Begin, next_v>(detail::is_one_element_range<Begin, next_v>{}, 1L);
  212. }
  213. template <class T, std::size_t Begin, std::size_t N>
  214. constexpr std::size_t fields_count_upper_bound(int, int) noexcept {
  215. return N - 1;
  216. }
  217. template <class T, std::size_t Begin, std::size_t N>
  218. constexpr auto fields_count_upper_bound(long, long) noexcept
  219. -> std::enable_if_t<(N > detail::fields_count_upper_bound_loose<T>()), std::size_t>
  220. {
  221. static_assert(
  222. !detail::is_initializable<T, detail::fields_count_upper_bound_loose<T>() + 1>(1L),
  223. "====================> Boost.PFR: Types with user specified constructors (non-aggregate initializable types) are not supported.");
  224. return detail::fields_count_upper_bound_loose<T>();
  225. }
  226. template <class T, std::size_t Begin, std::size_t N>
  227. constexpr auto fields_count_upper_bound(long, int) noexcept
  228. -> detail::enable_if_initializable_helper_t<T, N>
  229. {
  230. constexpr std::size_t next_optimal = Begin + (N - Begin) * 2;
  231. constexpr std::size_t next = detail::min_of_size_t(next_optimal, detail::fields_count_compiler_limitation_next(N));
  232. return detail::fields_count_upper_bound<T, Begin, next>(1L, 1L);
  233. }
  234. ///////////////////// Fields count lower bound linear search.
  235. // Template instantiation: depth is O(log(result)), count is O(result), cost is O(result^2).
  236. template <class T, std::size_t Begin, std::size_t Last, class RangeSize, std::size_t Result>
  237. constexpr std::size_t fields_count_lower_bound(RangeSize, size_t_<Result>) noexcept {
  238. return Result;
  239. }
  240. template <class T, std::size_t Begin, std::size_t Last>
  241. constexpr std::size_t fields_count_lower_bound(detail::one_element_range, size_t_<0> = {}) noexcept {
  242. static_assert(
  243. Begin == Last,
  244. "====================> Boost.PFR: Internal logic error."
  245. );
  246. return detail::is_initializable<T, Begin>(1L) ? Begin : 0;
  247. }
  248. template <class T, std::size_t Begin, std::size_t Last>
  249. constexpr std::size_t fields_count_lower_bound(detail::multi_element_range, size_t_<0> = {}) noexcept {
  250. // Binary partition to limit template depth.
  251. constexpr std::size_t middle = Begin + (Last - Begin) / 2;
  252. constexpr std::size_t result_maybe = detail::fields_count_lower_bound<T, Begin, middle>(
  253. detail::is_one_element_range<Begin, middle>{}
  254. );
  255. return detail::fields_count_lower_bound<T, middle + 1, Last>(
  256. detail::is_one_element_range<middle + 1, Last>{},
  257. size_t_<result_maybe>{}
  258. );
  259. }
  260. template <class T, std::size_t Begin, std::size_t Result>
  261. constexpr std::size_t fields_count_lower_bound_unbounded(int, size_t_<Result>) noexcept {
  262. return Result;
  263. }
  264. template <class T, std::size_t Begin>
  265. constexpr auto fields_count_lower_bound_unbounded(long, size_t_<0>) noexcept
  266. -> std::enable_if_t<(Begin >= detail::fields_count_upper_bound_loose<T>()), std::size_t>
  267. {
  268. static_assert(
  269. detail::is_initializable<T, detail::fields_count_upper_bound_loose<T>()>(1L),
  270. "====================> Boost.PFR: Type must be aggregate initializable.");
  271. return detail::fields_count_upper_bound_loose<T>();
  272. }
  273. template <class T, std::size_t Begin>
  274. constexpr std::size_t fields_count_lower_bound_unbounded(int, size_t_<0>) noexcept {
  275. constexpr std::size_t last = detail::min_of_size_t(Begin * 2, detail::fields_count_upper_bound_loose<T>()) - 1;
  276. constexpr std::size_t result_maybe = detail::fields_count_lower_bound<T, Begin, last>(
  277. detail::is_one_element_range<Begin, last>{}
  278. );
  279. return detail::fields_count_lower_bound_unbounded<T, last + 1>(1L, size_t_<result_maybe>{});
  280. }
  281. #endif
  282. ///////////////////// Choosing between array size, unbounded binary search, and linear search followed by unbounded binary search.
  283. template <class T>
  284. constexpr auto fields_count_dispatch(long, long, std::false_type /*are_preconditions_met*/) noexcept {
  285. return 0;
  286. }
  287. template <class T>
  288. constexpr auto fields_count_dispatch(long, long, std::true_type /*are_preconditions_met*/) noexcept
  289. -> std::enable_if_t<std::is_array<T>::value, std::size_t>
  290. {
  291. return sizeof(T) / sizeof(std::remove_all_extents_t<T>);
  292. }
  293. #if BOOST_PFR_USE_CPP26
  294. template<class T>
  295. constexpr auto fields_count_dispatch_impl(const T &t) noexcept
  296. {
  297. const auto &[... elts] = t;
  298. return std::integral_constant<std::size_t, sizeof...(elts)>{};
  299. }
  300. template<class T>
  301. constexpr auto fields_count_dispatch(long, int, std::true_type /*are_preconditions_met*/) noexcept
  302. -> std::enable_if_t<std::is_scalar<T>::value, std::size_t>
  303. {
  304. return 1;
  305. }
  306. template<class T>
  307. constexpr auto fields_count_dispatch(int, int, std::true_type /*are_preconditions_met*/) noexcept
  308. {
  309. return decltype(detail::fields_count_dispatch_impl(std::declval<const T &>()))::value;
  310. }
  311. #else
  312. template <class T>
  313. constexpr auto fields_count_dispatch(long, int, std::true_type /*are_preconditions_met*/) noexcept
  314. -> decltype(sizeof(T{}))
  315. {
  316. constexpr std::size_t typical_fields_count = 4;
  317. constexpr std::size_t last = detail::fields_count_upper_bound<T, typical_fields_count / 2, typical_fields_count>(1L, 1L);
  318. return detail::fields_count_binary_search<T, 0, last>(detail::is_one_element_range<0, last>{}, 1L);
  319. }
  320. template <class T>
  321. constexpr std::size_t fields_count_dispatch(int, int, std::true_type /*are_preconditions_met*/) noexcept {
  322. // T is not default aggregate initializable. This means that at least one of the members is not default-constructible.
  323. // Use linear search to find the smallest valid initializer, after which we unbounded binary search for the largest.
  324. constexpr std::size_t begin = detail::fields_count_lower_bound_unbounded<T, 1>(1L, size_t_<0>{});
  325. constexpr std::size_t last = detail::fields_count_upper_bound<T, begin, begin + 1>(1L, 1L);
  326. return detail::fields_count_binary_search<T, begin, last>(detail::is_one_element_range<begin, last>{}, 1L);
  327. }
  328. #endif
  329. ///////////////////// Returns fields count
  330. template <class T>
  331. constexpr std::size_t fields_count() noexcept {
  332. using type = std::remove_cv_t<T>;
  333. constexpr bool type_is_complete = detail::is_complete<type>::value;
  334. static_assert(
  335. type_is_complete,
  336. "====================> Boost.PFR: Type must be complete."
  337. );
  338. constexpr bool type_is_not_a_reference = !std::is_reference<type>::value
  339. || !type_is_complete // do not show assert if previous check failed
  340. ;
  341. static_assert(
  342. type_is_not_a_reference,
  343. "====================> Boost.PFR: Attempt to get fields count on a reference. This is not allowed because that could hide an issue and different library users expect different behavior in that case."
  344. );
  345. #if BOOST_PFR_HAS_GUARANTEED_COPY_ELISION
  346. constexpr bool type_fields_are_move_constructible = true;
  347. #else
  348. constexpr bool type_fields_are_move_constructible =
  349. std::is_copy_constructible<std::remove_all_extents_t<type>>::value || (
  350. std::is_move_constructible<std::remove_all_extents_t<type>>::value
  351. && std::is_move_assignable<std::remove_all_extents_t<type>>::value
  352. )
  353. || !type_is_not_a_reference // do not show assert if previous check failed
  354. ;
  355. static_assert(
  356. type_fields_are_move_constructible,
  357. "====================> Boost.PFR: Type and each field in the type must be copy constructible (or move constructible and move assignable)."
  358. );
  359. #endif // #if !BOOST_PFR_HAS_GUARANTEED_COPY_ELISION
  360. constexpr bool type_is_not_polymorphic = !std::is_polymorphic<type>::value;
  361. static_assert(
  362. type_is_not_polymorphic,
  363. "====================> Boost.PFR: Type must have no virtual function, because otherwise it is not aggregate initializable."
  364. );
  365. #ifdef __cpp_lib_is_aggregate
  366. constexpr bool type_is_aggregate =
  367. std::is_aggregate<type>::value // Does not return `true` for built-in types.
  368. || std::is_scalar<type>::value;
  369. static_assert(
  370. type_is_aggregate,
  371. "====================> Boost.PFR: Type must be aggregate initializable."
  372. );
  373. #else
  374. constexpr bool type_is_aggregate = true;
  375. #endif
  376. // Can't use the following. See the non_std_layout.cpp test.
  377. //#if !BOOST_PFR_USE_CPP17
  378. // static_assert(
  379. // std::is_standard_layout<type>::value, // Does not return `true` for structs that have non standard layout members.
  380. // "Type must be aggregate initializable."
  381. // );
  382. //#endif
  383. constexpr bool no_errors =
  384. type_is_complete && type_is_not_a_reference && type_fields_are_move_constructible
  385. && type_is_not_polymorphic && type_is_aggregate;
  386. constexpr std::size_t result
  387. = detail::fields_count_dispatch<type>(1L, 1L, std::integral_constant<bool, no_errors>{});
  388. detail::assert_first_not_base<type, result>(1L);
  389. #ifndef __cpp_lib_is_aggregate
  390. constexpr bool type_is_aggregate_initializable_n =
  391. detail::is_aggregate_initializable_n<type, result>::value // Does not return `true` for built-in types.
  392. || std::is_scalar<type>::value;
  393. static_assert(
  394. type_is_aggregate_initializable_n,
  395. "====================> Boost.PFR: Types with user specified constructors (non-aggregate initializable types) are not supported."
  396. );
  397. #else
  398. constexpr bool type_is_aggregate_initializable_n = true;
  399. #endif
  400. static_assert(
  401. result != 0 || std::is_empty<type>::value || std::is_fundamental<type>::value || std::is_reference<type>::value || !no_errors || !type_is_aggregate_initializable_n,
  402. "====================> Boost.PFR: If there's no other failed static asserts then something went wrong. Please report this issue to the github along with the structure you're reflecting."
  403. );
  404. return result;
  405. }
  406. }}} // namespace boost::pfr::detail
  407. #ifdef __clang__
  408. # pragma clang diagnostic pop
  409. #endif
  410. #endif // BOOST_PFR_DETAIL_FIELDS_COUNT_HPP