Skip to content

Commit 4c03d08

Browse files
committed
fix(reflexion): wrap field_ptr's NTTP to avoid Clang's P1907R1 gate
Passing a bare pointer-to-subobject directly as a non-type template argument needs P1907R1 ("generalized non-type template arguments"), an experimental C++20 extension GCC has accepted since GCC 11 but Clang only since Clang 18 (2024) - confirmed failing on this project's macOS wheel-building CI, which ships Clang 15. Giving the probe object a real definition (previous commit) didn't help: the gap is the NTTP kind itself, not whether the referenced object is defined. Wrapping the pointer in a one-member aggregate and letting CTAD deduce that as the template argument sidesteps it entirely - a class-type template argument built from a pointer member falls under P0732 ("class types as non-type template parameters"), a much older, widely-supported C++20 feature Clang has accepted for years. This is the same workaround Boost.PFR uses for its own C++20 field-name reflection (boost::pfr::detail::clang_wrapper_t), which documents the identical Clang<=16 limitation. Verified against the existing field_name test suite under both GCC 14 and Clang 19 locally, and confirmed the new code compiles cleanly under -pedantic-errors (no extension-usage diagnostics), consistent with no longer depending on the experimental extension. Cannot verify directly against Clang 15 itself - no such toolchain available here; this needs confirming on the actual CI run.
1 parent d376beb commit 4c03d08

1 file changed

Lines changed: 25 additions & 7 deletions

File tree

include/cpp_utils/reflexion/field_name.hpp

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,21 +61,39 @@ namespace cpp_utils::reflexion::details
6161
* reached for T that count_members<T> already accepts, which in practice means T is
6262
* default-constructible (an aggregate with a reference member, the one common way to defeat
6363
* default-construction, already fails count_members's own probe before getting here - see
64-
* reflection.hpp). A prior version of this left `instance` declared-but-never-defined to
64+
* reflection.hpp). An earlier version of this left `instance` declared-but-never-defined to
6565
* further avoid requiring default-constructibility; that turned out to be more than
66-
* count_members's own reach ever needed, and it made a member's address a converted constant
67-
* expression referring to storage that never receives a definition - some compilers
68-
* (confirmed failing: an Apple Clang toolchain on macOS wheel-building CI) reject that as an
69-
* invalid non-type template argument, even though it's never dereferenced. A genuinely
70-
* defined instance is uncontroversial, portable C++ and sidesteps the disagreement entirely.
66+
* count_members's own reach ever needed, so it was simplified to a genuine definition -
67+
* uncontroversial, portable C++.
7168
*/
7269
template <typename T>
7370
inline T static_probe_instance {};
7471

72+
/*
73+
* A bare pointer-to-subobject used directly as a non-type template argument needs P1907R1
74+
* ("generalized non-type template arguments" - WG21, still an experimental extension, not a
75+
* ratified feature: __cpp_nontype_template_args isn't bumped for it). GCC has accepted this
76+
* since GCC 11; Clang only since Clang 18 (2024) - confirmed failing on this project's
77+
* macOS wheel-building CI, which ships Clang 15. Wrapping the pointer in a one-member
78+
* aggregate and letting CTAD deduce *that* as the template argument sidesteps the gap
79+
* entirely: a class-type template argument built from a pointer member is covered by
80+
* P0732 ("class types as non-type template parameters"), a much older, widely-supported
81+
* C++20 feature - Clang has accepted it for years. Same workaround Boost.PFR uses for its
82+
* own C++20 field-name reflection (boost::pfr::detail::clang_wrapper_t), for the identical
83+
* reason - see that project's core_name20_static.hpp.
84+
*/
85+
template <typename T>
86+
struct ptr_wrapper
87+
{
88+
T value;
89+
};
90+
template <typename T>
91+
ptr_wrapper(T) -> ptr_wrapper<T>;
92+
7593
template <typename T, std::size_t N>
7694
consteval auto field_ptr()
7795
{
78-
return std::get<N>(cpp_utils_reflexion_field_ptr_tuple(static_probe_instance<T>));
96+
return ptr_wrapper { std::get<N>(cpp_utils_reflexion_field_ptr_tuple(static_probe_instance<T>)) };
7997
}
8098

8199
template <typename T, auto Ptr>

0 commit comments

Comments
 (0)