From 8383f5e20463f655ddae0e01b7535da53ecbe1f3 Mon Sep 17 00:00:00 2001 From: Antony Polukhin Date: Mon, 5 Jan 2026 23:18:42 +0300 Subject: [PATCH 01/15] Member pointer to index (WIP) --- include/boost/pfr/core.hpp | 56 +++++++++++++++++++++++++++++++ test/core/run/member_to_index.cpp | 26 ++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 test/core/run/member_to_index.cpp diff --git a/include/boost/pfr/core.hpp b/include/boost/pfr/core.hpp index d621f273..0a88cfa4 100644 --- a/include/boost/pfr/core.hpp +++ b/include/boost/pfr/core.hpp @@ -18,9 +18,11 @@ #include #include #include +#include #include + #if !defined(BOOST_PFR_INTERFACE_UNIT) #include #include // metaprogramming stuff @@ -277,6 +279,60 @@ constexpr detail::tie_from_structure_tuple tie_from_structure(Eleme return detail::tie_from_structure_tuple(args...); } +template +constexpr std::size_t index_of(const T& value, M T::*mem_ptr) { + constexpr auto size = boost::pfr::tuple_size_v; + std::size_t result = size; + + const void* target_address = std::addressof(value.*mem_ptr); + + boost::pfr::for_each_field(value, [&result, target_address](const auto& field, std::size_t idx) { + if (!std::is_same::value) { + return; + } + + if (result != size) { + // already found the answer + return; + } + + const void* filed_address = std::addressof(field); + if (target_address == filed_address) { + result = idx; + } + }); + + return result; +} + +// TODO: move into detail:: +template +auto strip_references(detail::sequence_tuple::tuple) -> detail::sequence_tuple::tuple; + +template +std::size_t index_of(M T::*mem_ptr) { + using tuple_type = detail::tuple_of_aligned_storage_t< + decltype(strip_references(detail::tie_as_tuple(std::declval()))) + >; + using converted_member_pointer_t = M tuple_type::*; + + constexpr tuple_type t{}; + + // TODO: not allowed in constexpr + unspecified behavior + auto mem_pointer = reinterpret_cast(mem_ptr); + + const void* pointer = std::addressof(t.*mem_pointer); + namespace sequence_tuple = boost::pfr::detail::sequence_tuple; + if (&sequence_tuple::get<0>(t) == pointer) { + return 0; + } else if (&sequence_tuple::get<1>(t) == pointer) { + return 1; + } else if (&sequence_tuple::get<2>(t) == pointer) { + return 2; + } + return 3; +} + BOOST_PFR_END_MODULE_EXPORT }} // namespace boost::pfr diff --git a/test/core/run/member_to_index.cpp b/test/core/run/member_to_index.cpp new file mode 100644 index 00000000..94deacb0 --- /dev/null +++ b/test/core/run/member_to_index.cpp @@ -0,0 +1,26 @@ +// Copyright (c) 2025-2026 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) + +#include + +#include +#include +#include + +struct Sample { + std::string x; + std::vector y; + std::string z; +}; + +int main() { + assert(boost::pfr::index_of(&Sample::x) == 0); + assert(boost::pfr::index_of(&Sample::y) == 1); + assert(boost::pfr::index_of(&Sample::z) == 2); + + static_assert(boost::pfr::index_of(Sample{}, &Sample::x) == 0); + static_assert(boost::pfr::index_of(Sample{}, &Sample::y) == 1); + static_assert(boost::pfr::index_of(Sample{}, &Sample::z) == 2); +} From 18e010f989f2dcaa0532a176a7bce7d6326270dd Mon Sep 17 00:00:00 2001 From: Antony Polukhin Date: Mon, 5 Jan 2026 23:27:14 +0300 Subject: [PATCH 02/15] cleanup --- include/boost/pfr/core.hpp | 33 +++---------------------------- test/core/run/member_to_index.cpp | 11 +++-------- 2 files changed, 6 insertions(+), 38 deletions(-) diff --git a/include/boost/pfr/core.hpp b/include/boost/pfr/core.hpp index 0a88cfa4..f7c69e85 100644 --- a/include/boost/pfr/core.hpp +++ b/include/boost/pfr/core.hpp @@ -18,7 +18,7 @@ #include #include #include -#include +#include #include @@ -280,7 +280,8 @@ constexpr detail::tie_from_structure_tuple tie_from_structure(Eleme } template -constexpr std::size_t index_of(const T& value, M T::*mem_ptr) { +constexpr std::size_t index_of(M T::*mem_ptr) { + constexpr const T& value = boost::pfr::detail::fake_object(); constexpr auto size = boost::pfr::tuple_size_v; std::size_t result = size; @@ -305,34 +306,6 @@ constexpr std::size_t index_of(const T& value, M T::*mem_ptr) { return result; } -// TODO: move into detail:: -template -auto strip_references(detail::sequence_tuple::tuple) -> detail::sequence_tuple::tuple; - -template -std::size_t index_of(M T::*mem_ptr) { - using tuple_type = detail::tuple_of_aligned_storage_t< - decltype(strip_references(detail::tie_as_tuple(std::declval()))) - >; - using converted_member_pointer_t = M tuple_type::*; - - constexpr tuple_type t{}; - - // TODO: not allowed in constexpr + unspecified behavior - auto mem_pointer = reinterpret_cast(mem_ptr); - - const void* pointer = std::addressof(t.*mem_pointer); - namespace sequence_tuple = boost::pfr::detail::sequence_tuple; - if (&sequence_tuple::get<0>(t) == pointer) { - return 0; - } else if (&sequence_tuple::get<1>(t) == pointer) { - return 1; - } else if (&sequence_tuple::get<2>(t) == pointer) { - return 2; - } - return 3; -} - BOOST_PFR_END_MODULE_EXPORT }} // namespace boost::pfr diff --git a/test/core/run/member_to_index.cpp b/test/core/run/member_to_index.cpp index 94deacb0..3def082f 100644 --- a/test/core/run/member_to_index.cpp +++ b/test/core/run/member_to_index.cpp @@ -5,7 +5,6 @@ #include -#include #include #include @@ -16,11 +15,7 @@ struct Sample { }; int main() { - assert(boost::pfr::index_of(&Sample::x) == 0); - assert(boost::pfr::index_of(&Sample::y) == 1); - assert(boost::pfr::index_of(&Sample::z) == 2); - - static_assert(boost::pfr::index_of(Sample{}, &Sample::x) == 0); - static_assert(boost::pfr::index_of(Sample{}, &Sample::y) == 1); - static_assert(boost::pfr::index_of(Sample{}, &Sample::z) == 2); + static_assert(boost::pfr::index_of(&Sample::x) == 0); + static_assert(boost::pfr::index_of(&Sample::y) == 1); + static_assert(boost::pfr::index_of(&Sample::z) == 2); } From 5f2aca762ced20a4ac89e6947bac53cac4aa4d77 Mon Sep 17 00:00:00 2001 From: Antony Polukhin Date: Tue, 6 Jan 2026 19:06:12 +0300 Subject: [PATCH 03/15] github editor is a piece of bad software --- include/boost/pfr/core.hpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/include/boost/pfr/core.hpp b/include/boost/pfr/core.hpp index f7c69e85..9bb07626 100644 --- a/include/boost/pfr/core.hpp +++ b/include/boost/pfr/core.hpp @@ -287,7 +287,18 @@ constexpr std::size_t index_of(M T::*mem_ptr) { const void* target_address = std::addressof(value.*mem_ptr); - boost::pfr::for_each_field(value, [&result, target_address](const auto& field, std::size_t idx) { + struct visitor { + template + void operator()(const T&) {} + + void operator()(const T& field) { + const void* filed_address = std::addressof(field); + if (target_address == filed_address) { + result = idx; + } + } +}; +stdboost::pfr::oost::pfr::for(ea:ch_, [&result, [&result, tar](const auto& field, std::size_t idx) { if (!std::is_same::value) { return; } From 24cd193eeebdc44e5f2b047a9f7554ab61890c93 Mon Sep 17 00:00:00 2001 From: Antony Polukhin Date: Tue, 6 Jan 2026 19:15:12 +0300 Subject: [PATCH 04/15] Update core.hpp --- include/boost/pfr/core.hpp | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/include/boost/pfr/core.hpp b/include/boost/pfr/core.hpp index 9bb07626..a6aa5e9d 100644 --- a/include/boost/pfr/core.hpp +++ b/include/boost/pfr/core.hpp @@ -282,39 +282,25 @@ constexpr detail::tie_from_structure_tuple tie_from_structure(Eleme template constexpr std::size_t index_of(M T::*mem_ptr) { constexpr const T& value = boost::pfr::detail::fake_object(); - constexpr auto size = boost::pfr::tuple_size_v; - std::size_t result = size; - - const void* target_address = std::addressof(value.*mem_ptr); struct visitor { template - void operator()(const T&) {} + void operator()(const T&, std::size_t) {} - void operator()(const T& field) { + void operator()(const T& field, std::size_t idx) { const void* filed_address = std::addressof(field); if (target_address == filed_address) { result = idx; } } -}; -stdboost::pfr::oost::pfr::for(ea:ch_, [&result, [&result, tar](const auto& field, std::size_t idx) { - if (!std::is_same::value) { - return; - } - - if (result != size) { - // already found the answer - return; - } - - const void* filed_address = std::addressof(field); - if (target_address == filed_address) { - result = idx; - } - }); - return result; + const void* target_address; + std::size_t result; + }; + visitor visit{std::addressof(value.*mem_ptr), boost::pfr::tuple_size_v}; + + boost::pfr::for_each_field(value, visit); + return visit.result; } BOOST_PFR_END_MODULE_EXPORT From e6bc1dd9f2fc3b01a28ae6163cb7e8f0590770d7 Mon Sep 17 00:00:00 2001 From: Antony Polukhin Date: Tue, 6 Jan 2026 19:17:42 +0300 Subject: [PATCH 05/15] Update core.hpp --- include/boost/pfr/core.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/pfr/core.hpp b/include/boost/pfr/core.hpp index a6aa5e9d..c9c824ab 100644 --- a/include/boost/pfr/core.hpp +++ b/include/boost/pfr/core.hpp @@ -287,7 +287,7 @@ constexpr std::size_t index_of(M T::*mem_ptr) { template void operator()(const T&, std::size_t) {} - void operator()(const T& field, std::size_t idx) { + void operator()(const M& field, std::size_t idx) { const void* filed_address = std::addressof(field); if (target_address == filed_address) { result = idx; From d96e25e829732b24d8eeab2b77f653c89611ae44 Mon Sep 17 00:00:00 2001 From: Antony Polukhin Date: Tue, 6 Jan 2026 19:38:42 +0300 Subject: [PATCH 06/15] Update core.hpp --- include/boost/pfr/core.hpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/include/boost/pfr/core.hpp b/include/boost/pfr/core.hpp index c9c824ab..c71efc49 100644 --- a/include/boost/pfr/core.hpp +++ b/include/boost/pfr/core.hpp @@ -279,10 +279,7 @@ constexpr detail::tie_from_structure_tuple tie_from_structure(Eleme return detail::tie_from_structure_tuple(args...); } -template -constexpr std::size_t index_of(M T::*mem_ptr) { - constexpr const T& value = boost::pfr::detail::fake_object(); - + template struct visitor { template void operator()(const T&, std::size_t) {} @@ -297,7 +294,12 @@ constexpr std::size_t index_of(M T::*mem_ptr) { const void* target_address; std::size_t result; }; - visitor visit{std::addressof(value.*mem_ptr), boost::pfr::tuple_size_v}; + +template +constexpr std::size_t index_of(M T::*mem_ptr) { + constexpr const T& value = boost::pfr::detail::fake_object(); + + visitor visit{std::addressof(value.*mem_ptr), boost::pfr::tuple_size_v}; boost::pfr::for_each_field(value, visit); return visit.result; From 361b8e7d02cf9db710869b2601c470becf8aff47 Mon Sep 17 00:00:00 2001 From: Antony Polukhin Date: Tue, 6 Jan 2026 20:01:16 +0300 Subject: [PATCH 07/15] Update core.hpp --- include/boost/pfr/core.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/boost/pfr/core.hpp b/include/boost/pfr/core.hpp index c71efc49..1ba3f78a 100644 --- a/include/boost/pfr/core.hpp +++ b/include/boost/pfr/core.hpp @@ -282,11 +282,11 @@ constexpr detail::tie_from_structure_tuple tie_from_structure(Eleme template struct visitor { template - void operator()(const T&, std::size_t) {} + constexpr void operator()(const T&, std::size_t) {} - void operator()(const M& field, std::size_t idx) { - const void* filed_address = std::addressof(field); - if (target_address == filed_address) { + constexpr void operator()(const M& field, std::size_t idx) { + const void* field_address = std::addressof(field); + if (target_address == field_address) { result = idx; } } From 93d02b86ce68d20627508d1dfd7655337c34d4b2 Mon Sep 17 00:00:00 2001 From: Antony Polukhin Date: Tue, 6 Jan 2026 20:43:12 +0300 Subject: [PATCH 08/15] Update for_each_field.hpp --- include/boost/pfr/detail/for_each_field.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/boost/pfr/detail/for_each_field.hpp b/include/boost/pfr/detail/for_each_field.hpp index 33b3ec1f..c2d92ed3 100644 --- a/include/boost/pfr/detail/for_each_field.hpp +++ b/include/boost/pfr/detail/for_each_field.hpp @@ -42,14 +42,14 @@ constexpr void for_each_field(T&& value, F&& func) { ::boost::pfr::detail::for_each_field_dispatcher( value, - [f = std::forward(func)](auto&& t) mutable { + [&func](auto&& t) mutable { // MSVC related workaround. Its lambdas do not capture constexprs. constexpr std::size_t fields_count_val_in_lambda = boost::pfr::detail::fields_count>(); ::boost::pfr::detail::for_each_field_impl( t, - std::forward(f), + std::forward(func), detail::make_index_sequence{}, std::is_rvalue_reference{} ); From c951f22eb731199f72f01bddc7a634d39736d330 Mon Sep 17 00:00:00 2001 From: Antony Polukhin Date: Tue, 6 Jan 2026 21:10:06 +0300 Subject: [PATCH 09/15] Update member_to_index.cpp --- test/core/run/member_to_index.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/core/run/member_to_index.cpp b/test/core/run/member_to_index.cpp index 3def082f..72b1333b 100644 --- a/test/core/run/member_to_index.cpp +++ b/test/core/run/member_to_index.cpp @@ -5,6 +5,7 @@ #include +#include #include #include @@ -15,7 +16,14 @@ struct Sample { }; int main() { +#if BOOST_PFR_USE_CPP17 || BOOST_PFR_USE_CPP26 static_assert(boost::pfr::index_of(&Sample::x) == 0); static_assert(boost::pfr::index_of(&Sample::y) == 1); static_assert(boost::pfr::index_of(&Sample::z) == 2); +#else + assert(boost::pfr::index_of(&Sample::x) == 0); + = 0 + assert(boost::pfr::index_of(&Sample::y) == 1); + assert(boost::pfr::index_of(&Sample::z) == 2); +#endif } From 9cb98e103a3c722883f96aa37255c36f6388963b Mon Sep 17 00:00:00 2001 From: Antony Polukhin Date: Tue, 6 Jan 2026 21:10:24 +0300 Subject: [PATCH 10/15] Update member_to_index.cpp --- test/core/run/member_to_index.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/test/core/run/member_to_index.cpp b/test/core/run/member_to_index.cpp index 72b1333b..f734ad96 100644 --- a/test/core/run/member_to_index.cpp +++ b/test/core/run/member_to_index.cpp @@ -22,7 +22,6 @@ int main() { static_assert(boost::pfr::index_of(&Sample::z) == 2); #else assert(boost::pfr::index_of(&Sample::x) == 0); - = 0 assert(boost::pfr::index_of(&Sample::y) == 1); assert(boost::pfr::index_of(&Sample::z) == 2); #endif From e360f0700d73f697da3a22c5bab22534d9d9c8b5 Mon Sep 17 00:00:00 2001 From: Antony Polukhin Date: Mon, 12 Jan 2026 20:32:43 +0300 Subject: [PATCH 11/15] cleanup --- include/boost/pfr.hpp | 1 + include/boost/pfr/core.hpp | 28 ------------ include/boost/pfr/index_of.hpp | 72 +++++++++++++++++++++++++++++++ test/core/run/member_to_index.cpp | 13 +++--- 4 files changed, 81 insertions(+), 33 deletions(-) create mode 100644 include/boost/pfr/index_of.hpp diff --git a/include/boost/pfr.hpp b/include/boost/pfr.hpp index ce447256..555bdf91 100644 --- a/include/boost/pfr.hpp +++ b/include/boost/pfr.hpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include diff --git a/include/boost/pfr/core.hpp b/include/boost/pfr/core.hpp index 1ba3f78a..d621f273 100644 --- a/include/boost/pfr/core.hpp +++ b/include/boost/pfr/core.hpp @@ -18,11 +18,9 @@ #include #include #include -#include #include - #if !defined(BOOST_PFR_INTERFACE_UNIT) #include #include // metaprogramming stuff @@ -279,32 +277,6 @@ constexpr detail::tie_from_structure_tuple tie_from_structure(Eleme return detail::tie_from_structure_tuple(args...); } - template - struct visitor { - template - constexpr void operator()(const T&, std::size_t) {} - - constexpr void operator()(const M& field, std::size_t idx) { - const void* field_address = std::addressof(field); - if (target_address == field_address) { - result = idx; - } - } - - const void* target_address; - std::size_t result; - }; - -template -constexpr std::size_t index_of(M T::*mem_ptr) { - constexpr const T& value = boost::pfr::detail::fake_object(); - - visitor visit{std::addressof(value.*mem_ptr), boost::pfr::tuple_size_v}; - - boost::pfr::for_each_field(value, visit); - return visit.result; -} - BOOST_PFR_END_MODULE_EXPORT }} // namespace boost::pfr diff --git a/include/boost/pfr/index_of.hpp b/include/boost/pfr/index_of.hpp new file mode 100644 index 00000000..2b2a4865 --- /dev/null +++ b/include/boost/pfr/index_of.hpp @@ -0,0 +1,72 @@ +// Copyright (c) 2025-2026 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) + +#ifndef BOOST_PFR_INDEX_OF_HPP +#define BOOST_PFR_INDEX_OF_HPP +#pragma once + +#include + +#if !defined(BOOST_USE_MODULES) || defined(BOOST_PFR_INTERFACE_UNIT) + +#include +#include + + +#if !defined(BOOST_PFR_INTERFACE_UNIT) +#include +#include // metaprogramming stuff +#endif + +/// \file boost/pfr/index_of.hpp +/// \copybrief boost::pfr::index_of + +namespace boost { namespace pfr { + +namespace detail { + +template +struct address_comparing_visitor { + template + constexpr void operator()(const T&, std::size_t) noexcept {} + + constexpr void operator()(const M& field, std::size_t idx) noexcept { + const void* field_address = std::addressof(field); + if (target_address == field_address) { + result = idx; + } + } + + const void* const target_address; + std::size_t result; +}; + +} + +BOOST_PFR_BEGIN_MODULE_EXPORT + +template +constexpr std::size_t index_of(M T::*mem_ptr) noexcept { + if (mem_ptr == nullptr) { + return ~static_cast(0); + } + + constexpr const T& value = boost::pfr::detail::fake_object(); + detail::address_comparing_visitor visitor{ + std::addressof(value.*mem_ptr), + ~static_cast(0) + }; + ::boost::pfr::detail::for_each_field(value, visitor); + + return visitor.result; +} + +BOOST_PFR_END_MODULE_EXPORT + +}} // namespace boost::pfr + +#endif // #if !defined(BOOST_USE_MODULES) || defined(BOOST_PFR_INTERFACE_UNIT) + +#endif // BOOST_PFR_INDEX_OF_HPP diff --git a/test/core/run/member_to_index.cpp b/test/core/run/member_to_index.cpp index f734ad96..22488216 100644 --- a/test/core/run/member_to_index.cpp +++ b/test/core/run/member_to_index.cpp @@ -3,9 +3,9 @@ // 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) -#include +#include -#include +#include #include #include @@ -21,8 +21,11 @@ int main() { static_assert(boost::pfr::index_of(&Sample::y) == 1); static_assert(boost::pfr::index_of(&Sample::z) == 2); #else - assert(boost::pfr::index_of(&Sample::x) == 0); - assert(boost::pfr::index_of(&Sample::y) == 1); - assert(boost::pfr::index_of(&Sample::z) == 2); + if (boost::pfr::index_of(&Sample::x) != 0) return 1; + if (boost::pfr::index_of(&Sample::y) != 1) return 2; + if (boost::pfr::index_of(&Sample::z) != 2) return 3; #endif + + decltype(&Sample::x) mem_ptr = nullptr; + if (boost::pfr::index_of(mem_ptr) != (std::numeric_limits::max)()) return 4; } From f20f0b0b79bf7961bca0c7d9c38f6dcb8a1085c7 Mon Sep 17 00:00:00 2001 From: Antony Polukhin Date: Mon, 12 Jan 2026 20:38:51 +0300 Subject: [PATCH 12/15] provide a portable version --- include/boost/pfr/index_of.hpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/include/boost/pfr/index_of.hpp b/include/boost/pfr/index_of.hpp index 2b2a4865..54f587ec 100644 --- a/include/boost/pfr/index_of.hpp +++ b/include/boost/pfr/index_of.hpp @@ -48,12 +48,11 @@ struct address_comparing_visitor { BOOST_PFR_BEGIN_MODULE_EXPORT template -constexpr std::size_t index_of(M T::*mem_ptr) noexcept { +constexpr std::size_t index_of(const T& value, M T::*mem_ptr) noexcept { if (mem_ptr == nullptr) { return ~static_cast(0); } - constexpr const T& value = boost::pfr::detail::fake_object(); detail::address_comparing_visitor visitor{ std::addressof(value.*mem_ptr), ~static_cast(0) @@ -63,6 +62,12 @@ constexpr std::size_t index_of(M T::*mem_ptr) noexcept { return visitor.result; } +template +constexpr std::size_t index_of(M T::*mem_ptr) noexcept { + constexpr const T& value = boost::pfr::detail::fake_object(); + return ::boost::pfr::index_of(value, mem_ptr); +} + BOOST_PFR_END_MODULE_EXPORT }} // namespace boost::pfr From 4f285bc9d4deafc701467754fcc80e719a65dcc6 Mon Sep 17 00:00:00 2001 From: Antony Polukhin Date: Thu, 15 Jan 2026 19:21:32 +0300 Subject: [PATCH 13/15] wip --- include/boost/pfr/index_of.hpp | 7 +++---- test/core/run/member_to_index.cpp | 26 ++++++++++++++++++-------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/include/boost/pfr/index_of.hpp b/include/boost/pfr/index_of.hpp index 54f587ec..17660087 100644 --- a/include/boost/pfr/index_of.hpp +++ b/include/boost/pfr/index_of.hpp @@ -11,7 +11,6 @@ #if !defined(BOOST_USE_MODULES) || defined(BOOST_PFR_INTERFACE_UNIT) -#include #include @@ -48,7 +47,7 @@ struct address_comparing_visitor { BOOST_PFR_BEGIN_MODULE_EXPORT template -constexpr std::size_t index_of(const T& value, M T::*mem_ptr) noexcept { +constexpr std::size_t index_of(M T::*mem_ptr, const T& value) noexcept { if (mem_ptr == nullptr) { return ~static_cast(0); } @@ -64,8 +63,8 @@ constexpr std::size_t index_of(const T& value, M T::*mem_ptr) noexcept { template constexpr std::size_t index_of(M T::*mem_ptr) noexcept { - constexpr const T& value = boost::pfr::detail::fake_object(); - return ::boost::pfr::index_of(value, mem_ptr); + constexpr T value{}; + return ::boost::pfr::index_of(mem_ptr, value); } BOOST_PFR_END_MODULE_EXPORT diff --git a/test/core/run/member_to_index.cpp b/test/core/run/member_to_index.cpp index 22488216..e50b4403 100644 --- a/test/core/run/member_to_index.cpp +++ b/test/core/run/member_to_index.cpp @@ -15,17 +15,27 @@ struct Sample { std::string z; }; +struct TrivialSample { + int x; + short y; + bool z; +}; + int main() { #if BOOST_PFR_USE_CPP17 || BOOST_PFR_USE_CPP26 - static_assert(boost::pfr::index_of(&Sample::x) == 0); - static_assert(boost::pfr::index_of(&Sample::y) == 1); - static_assert(boost::pfr::index_of(&Sample::z) == 2); -#else - if (boost::pfr::index_of(&Sample::x) != 0) return 1; - if (boost::pfr::index_of(&Sample::y) != 1) return 2; - if (boost::pfr::index_of(&Sample::z) != 2) return 3; + static_assert(boost::pfr::index_of(&TrivialSample::x) == 0); + static_assert(boost::pfr::index_of(&TrivialSample::y) == 1); + static_assert(boost::pfr::index_of(&TrivialSample::z) == 2); #endif + if (boost::pfr::index_of(&Sample::x, {}) != 0) return 1; + if (boost::pfr::index_of(&Sample::y, {}) != 1) return 2; + if (boost::pfr::index_of(&Sample::z, {}) != 2) return 3; + decltype(&Sample::x) mem_ptr = nullptr; - if (boost::pfr::index_of(mem_ptr) != (std::numeric_limits::max)()) return 4; + if (boost::pfr::index_of(mem_ptr, {}) != (std::numeric_limits::max)()) return 4; + + if (boost::pfr::index_of(&TrivialSample::x) != 0) return 11; + if (boost::pfr::index_of(&TrivialSample::y) != 1) return 12; + if (boost::pfr::index_of(&TrivialSample::z) != 2) return 13; } From 5d30e2d14bfd5d963bae4faeaaf8725ac68cc3c2 Mon Sep 17 00:00:00 2001 From: Antony Polukhin Date: Thu, 22 Jan 2026 12:22:45 +0300 Subject: [PATCH 14/15] wip --- include/boost/pfr.hpp | 1 + include/boost/pfr/index_of.hpp | 15 ++++-- .../is_constexpr_default_constructible.hpp | 49 +++++++++++++++++++ test/core/run/is_constexpr_constructible.cpp | 41 ++++++++++++++++ test/core/run/member_to_index.cpp | 17 +++++++ 5 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 include/boost/pfr/is_constexpr_default_constructible.hpp create mode 100644 test/core/run/is_constexpr_constructible.cpp diff --git a/include/boost/pfr.hpp b/include/boost/pfr.hpp index 555bdf91..8f51ce84 100644 --- a/include/boost/pfr.hpp +++ b/include/boost/pfr.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include diff --git a/include/boost/pfr/index_of.hpp b/include/boost/pfr/index_of.hpp index 17660087..df994e4e 100644 --- a/include/boost/pfr/index_of.hpp +++ b/include/boost/pfr/index_of.hpp @@ -12,11 +12,12 @@ #if !defined(BOOST_USE_MODULES) || defined(BOOST_PFR_INTERFACE_UNIT) #include +#include #if !defined(BOOST_PFR_INTERFACE_UNIT) #include -#include // metaprogramming stuff +#include // std::addressof #endif /// \file boost/pfr/index_of.hpp @@ -63,8 +64,16 @@ constexpr std::size_t index_of(M T::*mem_ptr, const T& value) noexcept { template constexpr std::size_t index_of(M T::*mem_ptr) noexcept { - constexpr T value{}; - return ::boost::pfr::index_of(mem_ptr, value); + static_assert( + boost::pfr::is_constexpr_default_constructible_v, + "====================> Boost.PFR: T should be default constructible in constant evaluations. " + "Either add `constexpr` to constructors of member types or use the boost::pfr::index_of() overload " + "with explicit `const T& value` parameter." + ); + + // boost::pfr::is_constexpr_default_constructible_v gives a faint hope that the + // compiler would be able to optimize away the temporary `T{}`. + return ::boost::pfr::index_of(mem_ptr, T{}); } BOOST_PFR_END_MODULE_EXPORT diff --git a/include/boost/pfr/is_constexpr_default_constructible.hpp b/include/boost/pfr/is_constexpr_default_constructible.hpp new file mode 100644 index 00000000..05976d14 --- /dev/null +++ b/include/boost/pfr/is_constexpr_default_constructible.hpp @@ -0,0 +1,49 @@ +// Copyright (c) 2025-2026 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) + +#ifndef BOOST_PFR_IS_CONSTEXPR_DEFAULT_CONSTRUCTIBLE_HPP +#define BOOST_PFR_IS_CONSTEXPR_DEFAULT_CONSTRUCTIBLE_HPP +#pragma once + +#include + +#if !defined(BOOST_USE_MODULES) || defined(BOOST_PFR_INTERFACE_UNIT) + +#if !defined(BOOST_PFR_INTERFACE_UNIT) +#include +#endif + +namespace boost { namespace pfr { + +namespace detail { + template (T{}), 0)> + constexpr std::true_type is_constexpr_default_constructible(long) noexcept { + return {}; + } + + template + constexpr std::false_type is_constexpr_default_constructible(int) noexcept { + return {}; + } +} // namespace detail + +BOOST_PFR_BEGIN_MODULE_EXPORT + +template +struct is_constexpr_default_constructible + : decltype(detail::is_constexpr_default_constructible(1L)) +{}; + +template +constexpr bool is_constexpr_default_constructible_v + = decltype(detail::is_constexpr_default_constructible(1L))::value; + +BOOST_PFR_END_MODULE_EXPORT + +}} // namespace boost::pfr + +#endif // #if !defined(BOOST_USE_MODULES) || defined(BOOST_PFR_INTERFACE_UNIT) + +#endif // BOOST_PFR_IS_CONSTEXPR_DEFAULT_CONSTRUCTIBLE_HPP diff --git a/test/core/run/is_constexpr_constructible.cpp b/test/core/run/is_constexpr_constructible.cpp new file mode 100644 index 00000000..2eca8665 --- /dev/null +++ b/test/core/run/is_constexpr_constructible.cpp @@ -0,0 +1,41 @@ +// Copyright (c) 2025-2026 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) + +#include + +#include + +struct Yes { + int a; + double b; + char c; +}; + +struct Yes2 { + int a; + double b; + char c; + Yes d[42]; +}; + +class MyClass{ +public: + MyClass(); + ~MyClass(); +}; + +struct No { + int a; + double b; + char c; + std::string str; + MyClass mc; +}; + +int main() { + static_assert(is_constexpr_default_constructible_v); + static_assert(is_constexpr_default_constructible_v); + static_assert(!is_constexpr_default_constructible_v); +} diff --git a/test/core/run/member_to_index.cpp b/test/core/run/member_to_index.cpp index e50b4403..694c9da4 100644 --- a/test/core/run/member_to_index.cpp +++ b/test/core/run/member_to_index.cpp @@ -21,6 +21,19 @@ struct TrivialSample { bool z; }; +template +int test_if_constexpr_default_constructible(std::false_type) { + return 0; +} + +template +int test_if_constexpr_default_constructible(std::true_type) { + if (boost::pfr::index_of(&T::x) != 0) return 31; + if (boost::pfr::index_of(&T::y) != 1) return 32; + if (boost::pfr::index_of(&T::z) != 2) return 33; + return 0; +} + int main() { #if BOOST_PFR_USE_CPP17 || BOOST_PFR_USE_CPP26 static_assert(boost::pfr::index_of(&TrivialSample::x) == 0); @@ -38,4 +51,8 @@ int main() { if (boost::pfr::index_of(&TrivialSample::x) != 0) return 11; if (boost::pfr::index_of(&TrivialSample::y) != 1) return 12; if (boost::pfr::index_of(&TrivialSample::z) != 2) return 13; + + return test_if_constexpr_default_constructible( + boost::pfr::is_constexpr_default_constructible{} + ); } From 4328226b02bde2c589fee125868a57daa466cd36 Mon Sep 17 00:00:00 2001 From: Antony Polukhin Date: Thu, 22 Jan 2026 12:34:49 +0300 Subject: [PATCH 15/15] fix --- test/core/run/is_constexpr_constructible.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/core/run/is_constexpr_constructible.cpp b/test/core/run/is_constexpr_constructible.cpp index 2eca8665..5a22bf6e 100644 --- a/test/core/run/is_constexpr_constructible.cpp +++ b/test/core/run/is_constexpr_constructible.cpp @@ -35,7 +35,7 @@ struct No { }; int main() { - static_assert(is_constexpr_default_constructible_v); - static_assert(is_constexpr_default_constructible_v); - static_assert(!is_constexpr_default_constructible_v); + static_assert(is_constexpr_default_constructible_v, ""); + static_assert(is_constexpr_default_constructible_v, ""); + static_assert(!is_constexpr_default_constructible_v, ""); }