https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126361
Bug ID: 126361
Summary: [C++26][reflection] std::meta::reflect_constant throws
meta::exception when target class has a copy
constructor that reads a member of the source object
Product: gcc
Version: 16.1.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: c++
Assignee: unassigned at gcc dot gnu.org
Reporter: painthingy at gmail dot com
Target Milestone: ---
Version: g++ (Compiler-Explorer-Build-gcc--binutils-2.44) 16.1.0
Target: x86_64-linux-gnu
Command line: -std=c++26 -freflection -Wall -Wextra -O0
Description:
std::meta::reflect_constant on a class-type argument will invoke the class's
copy constructor as part of constant evaluation.
GCC throws std::meta::exception from reflect_constant if the copy constructor's
body reads a member of the source object being copied, even though the exact
same copy constructor works fine when GCC reaches it through a different path
(constant_of/substitute on a static-storage-duration variable template
specialization, Part 2 below).
*std::addressof(x) must be equivalent to x; whether this read is accepted
should not depend on which internal code path led to the copy constructor being
evaluated. Since Part 2 shows GCC can form a reflected constant of this exact
type using this exact copy constructor, the failure in Part 1 isn't a general
restriction on reflecting class types with this kind of constructor — it looks
like a bug specific to how GCC evaluates reads for objects local to the
currently-executing constant expression.
Clang's experimental P2996 branch accepts both programs unmodified:
clang version 21.0.0git (https://github.com/Bloomberg/clang-p2996
7220baffd57ea5b0f8cf59bee494dd5b7cc2b748)
flags: -std=c++26 -fparameter-reflection -freflection-latest -O0
Part 1 — minimal failing case ( https://godbolt.org/z/ddaeT1h66):
```cpp
#include <meta>
#include <memory>
struct Probe {
int value = 42;
constexpr Probe() = default;
constexpr Probe(const Probe& other) {
value = (std::addressof(other.value) == nullptr) + 42;
[[maybe_unused]] auto unused = *std::addressof(other.value); // remove
-> GCC accepts
}
};
consteval {
std::meta::reflect_constant(Probe{});
}
int main() {}
```
GCC rejects this with:
error: uncaught exception of type 'std::meta::exception'; 'what()':
'reflect_constant failed'
raised on the std::meta::reflect_constant(Probe{}); line. Deleting the marked
unused line (keeping the addressof(...) == nullptr comparison) makes GCC accept
the program.
Part 2 — same type, same copy constructor, Probe reached via
constant_of/substitute instead of directly — GCC accepts
(https://godbolt.org/z/EKhdvnEY7):
```cpp
#include <meta>
#include <memory>
struct Probe {
int value;
constexpr Probe() : value(42) {}
constexpr Probe(int v) : value(v) {}
constexpr Probe(const Probe& other) {
value = (std::addressof(other.value) == nullptr) + 42;
[[maybe_unused]] auto unused = *std::addressof(other.value);
}
};
// A variable template whose specializations are Probe objects.
template<auto... Args>
constexpr auto storage = Probe{Args...};
// make a static-storage-duration variable then get the constant reflection
consteval std::meta::info reflect_probe(const Probe& p) {
return constant_of(
substitute(^^storage, {std::meta::reflect_constant(p.value)}));
}
template<Probe V>
requires (V.value == 42)
struct sink {};
consteval {
auto c = reflect_probe(Probe{42});
[[maybe_unused]] auto h = substitute(^^sink, {c}); // fine on GCC
}
int main() {}
```
This compiles cleanly under the same flags, with the identical copy constructor
body (including the *std::addressof(...) read).
Expected result: Part 1 should be accepted, matching Clang and matching GCC's
own behavior in Part 2.
Actual result: Part 1 is rejected with the meta::exception shown above.
Other cases:
https://godbolt.org/z/fjbc4M9Tz
https://godbolt.org/z/6vzjj5znY