Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions doc/pfr.qbk
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[library Boost.PFR
[quickbook 1.6]
[version 2.3]
[version 2.4]
[copyright 2016-2026 Antony Polukhin]
[category Language Features Emulation]
[license
Expand Down Expand Up @@ -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_REFLECTION*] [Define to `1` if you wish to override Boost.PFR choice and use C++26 Reflection. Define to `0` to override Boost.PFR choice and disable C++26 Reflection usage.]]
[[*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.]]
Expand Down Expand Up @@ -594,11 +595,13 @@ 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_CPP26 == 1]:
* [*BOOST_PFR_USE_CPP26_REFLECTION == 1]:
# at compile-time: C++26 Reflection is used to make reference to field
* [*BOOST_PFR_USE_CPP26_REFLECTION == 0 && 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]:
* [*BOOST_PFR_USE_CPP26_REFLECTION == 0 && 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_CPP26 == 0 && BOOST_PFR_USE_CPP17 == 0 && BOOST_PFR_USE_LOOPHOLE == 1]:
* [*BOOST_PFR_USE_CPP26_REFLECTION == 0 && 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
Expand All @@ -621,6 +624,8 @@ Description of the [*BOOST_PFR_USE_LOOPHOLE == 1] technique by its inventor Alex

[h2 Field name retrieval]

If [*BOOST_PFR_USE_CPP26_REFLECTION == 1] then C++26 Reflection is used to get names. Otherwise:

# at compile-time:
* Get references to members of an object of type `T` in `constexpr` function
* Feed the reference from previous as a template parameter to a `constexpr` function with `template <auto member_ptr>`.
Expand Down
11 changes: 10 additions & 1 deletion include/boost/pfr/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@
#endif
#endif

#ifndef BOOST_PFR_USE_CPP26_REFLECTION
#ifdef __cpp_lib_reflection
// TODO: experimental. Not enabled by default for now
#define BOOST_PFR_USE_CPP26_REFLECTION 0
#else
#define BOOST_PFR_USE_CPP26_REFLECTION 0
#endif
#endif

#ifndef BOOST_PFR_USE_CPP17
# ifdef __cpp_structured_bindings
# define BOOST_PFR_USE_CPP17 1
Expand Down Expand Up @@ -118,7 +127,7 @@
|| (defined(__clang_major__) && __clang_major__ >= 12)
# define BOOST_PFR_CORE_NAME_ENABLED 1
# else
# define BOOST_PFR_CORE_NAME_ENABLED 0
# define BOOST_PFR_CORE_NAME_ENABLED BOOST_PFR_USE_CPP26_REFLECTION
# endif
# else
# define BOOST_PFR_CORE_NAME_ENABLED 0
Expand Down
22 changes: 14 additions & 8 deletions include/boost/pfr/core.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ BOOST_PFR_BEGIN_MODULE_EXPORT
/// \endcode
template <std::size_t I, class T>
constexpr decltype(auto) get(const T& val) noexcept {
#if BOOST_PFR_USE_CPP26
#if BOOST_PFR_USE_CPP26_REFLECTION
return detail::reference_by_index<I>(val);
#elif BOOST_PFR_USE_CPP26
const auto& [... members] = val;
return std::forward_like<const T &>(members...[I]);
#else
Expand All @@ -67,15 +69,17 @@ constexpr decltype(auto) get(T& val
, std::enable_if_t<std::is_assignable<T, T>::value>* = nullptr
#endif
) noexcept {
#if BOOST_PFR_USE_CPP26
#if BOOST_PFR_USE_CPP26_REFLECTION
return detail::reference_by_index<I>(val);
#elif BOOST_PFR_USE_CPP26
auto& [... members] = val;
return std::forward_like<T &>(members...[I]);
#else
return detail::sequence_tuple::get<I>( detail::tie_as_tuple(val) );
#endif
}

#if !BOOST_PFR_USE_CPP17 && !BOOST_PFR_USE_CPP26
#if !BOOST_PFR_USE_CPP17 && !BOOST_PFR_USE_CPP26 && !BOOST_PFR_USE_CPP26_REFLECTION
/// \overload get
template <std::size_t I, class T>
constexpr auto get(T&, std::enable_if_t<!std::is_assignable<T, T>::value>* = nullptr) noexcept {
Expand All @@ -88,7 +92,9 @@ constexpr auto get(T&, std::enable_if_t<!std::is_assignable<T, T>::value>* = nul
/// \overload get
template <std::size_t I, class T>
constexpr auto get(T&& val, std::enable_if_t< std::is_rvalue_reference<T&&>::value>* = nullptr) noexcept {
#if BOOST_PFR_USE_CPP26
#if BOOST_PFR_USE_CPP26_REFLECTION
return std::move(detail::reference_by_index<I>(val));
#elif BOOST_PFR_USE_CPP26
auto&& [... members] = std::forward<T>(val);
return std::move(members...[I]);
#else
Expand All @@ -107,14 +113,14 @@ constexpr const U& get(const T& val) noexcept {
/// \overload get
template <class U, class T>
constexpr U& get(T& val
#if !BOOST_PFR_USE_CPP17 && !BOOST_PFR_USE_CPP26
#if !BOOST_PFR_USE_CPP17 && !BOOST_PFR_USE_CPP26 && !BOOST_PFR_USE_CPP26_REFLECTION
, std::enable_if_t<std::is_assignable<T, T>::value>* = nullptr
#endif
) noexcept {
return detail::sequence_tuple::get_by_type_impl<U&>( detail::tie_as_tuple(val) );
}

#if !BOOST_PFR_USE_CPP17 && !BOOST_PFR_USE_CPP26
#if !BOOST_PFR_USE_CPP17 && !BOOST_PFR_USE_CPP26 && !BOOST_PFR_USE_CPP26_REFLECTION
/// \overload get
template <class U, class T>
constexpr U& get(T&, std::enable_if_t<!std::is_assignable<T, T>::value>* = nullptr) noexcept {
Expand Down Expand Up @@ -207,7 +213,7 @@ constexpr auto structure_tie(const T& val) noexcept {
/// \overload structure_tie
template <class T>
constexpr auto structure_tie(T& val
#if !BOOST_PFR_USE_CPP17 && !BOOST_PFR_USE_CPP26
#if !BOOST_PFR_USE_CPP17 && !BOOST_PFR_USE_CPP26 && !BOOST_PFR_USE_CPP26_REFLECTION
, std::enable_if_t<std::is_assignable<T, T>::value>* = nullptr
#endif
) noexcept {
Expand All @@ -220,7 +226,7 @@ constexpr auto structure_tie(T& val
#endif
}

#if !BOOST_PFR_USE_CPP17 && !BOOST_PFR_USE_CPP26
#if !BOOST_PFR_USE_CPP17 && !BOOST_PFR_USE_CPP26 && !BOOST_PFR_USE_CPP26_REFLECTION
/// \overload structure_tie
template <class T>
constexpr auto structure_tie(T&, std::enable_if_t<!std::is_assignable<T, T>::value>* = nullptr) noexcept {
Expand Down
20 changes: 19 additions & 1 deletion include/boost/pfr/core_name.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

#if !defined(BOOST_PFR_INTERFACE_UNIT)
#include <cstddef> // for std::size_t
#include <string_view>
#endif

/// \file boost/pfr/core_name.hpp
Expand Down Expand Up @@ -106,7 +107,24 @@ names_as_array() noexcept {
/// \endcode
template <class T, class F>
constexpr void for_each_field_with_name(T&& value, F&& func) {
return boost::pfr::detail::for_each_field_with_name(std::forward<T>(value), std::forward<F>(func));
#if BOOST_PFR_CORE_NAME_ENABLED
return boost::pfr::detail::for_each_field(
std::forward<T>(value),
[&func](auto&& field, auto index) {
using IndexType = decltype(index);
using FieldType = decltype(field);
constexpr auto name = boost::pfr::detail::get_name<std::remove_reference_t<T>, IndexType::value>();
if constexpr (std::is_invocable_v<F, std::string_view, FieldType, IndexType>) {
std::forward<F>(func)(name, std::forward<FieldType>(field), index);
} else {
std::forward<F>(func)(name, std::forward<FieldType>(field));
}
});
#else
boost::pfr::detail::report_name_reflection_mising_requirement<T>();
(void)value;
(void)func;
#endif
}

BOOST_PFR_END_MODULE_EXPORT
Expand Down
4 changes: 3 additions & 1 deletion include/boost/pfr/detail/core.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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_CPP26
#if BOOST_PFR_USE_CPP26_REFLECTION
#include <boost/pfr/detail/core26_reflection.hpp>
#elif BOOST_PFR_USE_CPP26
#include <boost/pfr/detail/core26.hpp>
#elif BOOST_PFR_USE_CPP17
# include <boost/pfr/detail/core17.hpp>
Expand Down
64 changes: 64 additions & 0 deletions include/boost/pfr/detail/core26_reflection.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// 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_DETAIL_CORE26_REFLECTION_HPP
#define BOOST_PFR_DETAIL_CORE26_REFLECTION_HPP
#pragma once

#include <boost/pfr/detail/config.hpp>
#include <boost/pfr/detail/fields_count.hpp>
#include <boost/pfr/detail/make_integer_sequence.hpp>
#include <boost/pfr/detail/sequence_tuple.hpp>

#if !defined(BOOST_PFR_INTERFACE_UNIT)
#include <type_traits>
#include <meta>
#endif

namespace boost::pfr::detail {

template <std::size_t I, typename T>
consteval decltype(auto) reference_by_index(T &val) noexcept {
return val.[:
nonstatic_data_members_of(
^^T,
std::meta::access_context::current()
).at(I)
:];
}

template<class T, std::size_t... I>
constexpr auto tie_as_tuple_impl(T &val, std::index_sequence<I...>) noexcept {
return sequence_tuple::tuple<
decltype(boost::pfr::detail::reference_by_index<I>(val))...
>{
boost::pfr::detail::reference_by_index<I>(val)...
};

}

template<class T>
constexpr auto tie_as_tuple(T &val) noexcept {
return detail::tie_as_tuple_impl(
val,
detail::make_index_sequence<detail::fields_count<T>()>{}
);

}

template <class T, class F, std::size_t... I>
constexpr void for_each_field_dispatcher(T& t, F&& f, std::index_sequence<I...>) {
static_assert(
!std::is_union<T>::value,
"====================> Boost.PFR: For safety reasons it is forbidden to reflect unions. See `Reflection of unions` section in the docs for more info."
);
std::forward<F>(f)(
detail::tie_as_tuple(t)
);
}

} // namespace boost::pfr::detail

#endif
4 changes: 3 additions & 1 deletion include/boost/pfr/detail/core_name.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
//
// The whole functional of extracting field's names is build on top of those
// two functions.
#if BOOST_PFR_CORE_NAME_ENABLED
#if BOOST_PFR_USE_CPP26_REFLECTION
#include <boost/pfr/detail/core_name26_reflection.hpp>
#elif BOOST_PFR_CORE_NAME_ENABLED
#include <boost/pfr/detail/core_name20_static.hpp>
#else
#include <boost/pfr/detail/core_name14_disabled.hpp>
Expand Down
23 changes: 7 additions & 16 deletions include/boost/pfr/detail/core_name14_disabled.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,35 +17,26 @@

namespace boost { namespace pfr { namespace detail {

template <class T, std::size_t I>
constexpr auto get_name() noexcept {
template <class T>
constexpr void report_name_reflection_mising_requirement() noexcept {
static_assert(
sizeof(T) && false,
"====================> Boost.PFR: Field's names extracting functionality requires C++20."
);
}

template <class T, std::size_t I>
constexpr auto get_name() noexcept {
detail::report_name_reflection_mising_requirement<T>();
return nullptr;
}

template <class T>
constexpr auto tie_as_names_tuple() noexcept {
static_assert(
sizeof(T) && false,
"====================> Boost.PFR: Field's names extracting functionality requires C++20."
);

detail::report_name_reflection_mising_requirement<T>();
return detail::sequence_tuple::make_sequence_tuple();
}


template <class T, class F>
constexpr void for_each_field_with_name(T&& /* value */, F&& /* func */) {
static_assert(
sizeof(T) && false,
"====================> Boost.PFR: Field's names extracting functionality requires C++20."
);
}

}}} // namespace boost::pfr::detail

#endif // BOOST_PFR_DETAIL_CORE_NAME14_DISABLED_HPP
Expand Down
16 changes: 0 additions & 16 deletions include/boost/pfr/detail/core_name20_static.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -241,22 +241,6 @@ constexpr auto tie_as_names_tuple() noexcept {
return detail::tie_as_names_tuple_impl<T>(detail::make_index_sequence<detail::fields_count<T>()>{});
}

template <class T, class F>
constexpr void for_each_field_with_name(T&& value, F&& func) {
return boost::pfr::detail::for_each_field(
std::forward<T>(value),
[&func](auto&& field, auto index) mutable {
using IndexType = decltype(index);
using FieldType = decltype(field);
constexpr auto name = boost::pfr::detail::get_name<std::remove_reference_t<T>, IndexType::value>();
if constexpr (std::is_invocable_v<F, std::string_view, FieldType, IndexType>) {
std::forward<F>(func)(name, std::forward<FieldType>(field), index);
} else {
std::forward<F>(func)(name, std::forward<FieldType>(field));
}
});
}

}}} // namespace boost::pfr::detail

#endif // BOOST_PFR_DETAIL_CORE_NAME20_STATIC_HPP
Expand Down
63 changes: 63 additions & 0 deletions include/boost/pfr/detail/core_name26_reflection.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// 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)


// Initial implementation by Bela Schaum, https://github.com/schaumb
// The way to make it union and UB free by X-Ryl669, https://github.com/X-Ryl669
//

#ifndef BOOST_PFR_DETAIL_CORE_NAME26_REFLECTION_HPP
#define BOOST_PFR_DETAIL_CORE_NAME26_REFLECTION_HPP
#pragma once

#include <boost/pfr/detail/config.hpp>

#include <boost/pfr/detail/fields_count.hpp>
#include <boost/pfr/detail/for_each_field.hpp>
#include <boost/pfr/detail/make_integer_sequence.hpp>
#include <boost/pfr/detail/sequence_tuple.hpp>

#if !defined(BOOST_PFR_INTERFACE_UNIT)
#include <type_traits>
#include <array>
#include <memory> // for std::addressof
#include <meta>
#endif

namespace boost { namespace pfr { namespace detail {

template <class T, std::size_t I>
constexpr std::string_view get_name() noexcept {
static_assert(
!std::is_union<T>::value,
"====================> Boost.PFR: For safety reasons it is forbidden to reflect unions. See `Reflection of unions` section in the docs for more info."
);
static_assert(
!std::is_array<T>::value,
"====================> Boost.PFR: It is impossible to extract name from old C array since it doesn't have named members"
);

return identifier_of(
nonstatic_data_members_of(
^^T,
std::meta::access_context::current()
).at(I)
);
}

template <class T, std::size_t... I>
constexpr auto tie_as_names_tuple_impl(std::index_sequence<I...>) noexcept {
return detail::sequence_tuple::make_sequence_tuple(detail::get_name<T, I>()...);
}

template <class T>
constexpr auto tie_as_names_tuple() noexcept {
return detail::tie_as_names_tuple_impl<T>(detail::make_index_sequence<detail::fields_count<T>()>{});
}

}}} // namespace boost::pfr::detail

#endif // BOOST_PFR_DETAIL_CORE_NAME20_STATIC_HPP

Loading