https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127002
Bug ID: 127002
Summary: Reflection metadata from
define_static_array/define_static_string is conflated
across distinct local classes sharing the same name
and layout
Product: gcc
Version: 16.2.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: c++
Assignee: unassigned at gcc dot gnu.org
Reporter: dikachess at gmail dot com
Target Milestone: ---
# Reflection metadata from `define_static_array`/`define_static_string` is
# conflated across distinct local classes sharing the same name and layout
## Environment
- Compiler: g++ 16.2.0
team@localhost:/home/team$ g++ -v
Utilisation des specs internes.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/opt/gcc-16.2/libexec/gcc/x86_64-pc-linux-gnu/16.2.0/lto-wrapper
Cible : x86_64-pc-linux-gnu
Configuré avec: ../configure --prefix=/opt/gcc-16.2 --enable-languages=c,c++
--disable-multilib --disable-bootstrap --enable-checking=release
--enable-libstdcxx-backtrace --enable-host-shared --enable-threads=posix
--enable-plugin
Modèle de thread: posix
Algorithmes de compression LTO supportés: zlib zstd
gcc version 16.2.0 (GCC)
- Branch/build: master (github.com/gcc-miror/gcc, branch `master`)
CONFIRMED: the issue is reproducible with the mainline post-merge build.
- Flags used to reproduce: -std=c++26 -freflection
(originally observed with additionally -fmodules-ts, but that flag is not
required to reproduce — see minimal repro below, built without it)
- OS: Oracle Linux Server 10.2
Kernel: 6.12.0-205.92.4.2.el10uek.x86_64
Architecture: x86_64
## Summary
When two distinct local classes (i.e. classes defined in the body of two
different functions) share the same name and the same non-static data
member layout within a single translation unit, reflecting their members
via `std::meta::nonstatic_data_members_of` and splicing them with
`obj.[:member:]` inside a function template instantiated separately for
each type causes the compiler to resolve the splice against the *wrong*
type's member on the second instantiation, producing a spurious
"invalid use of non-static data member" diagnostic.
Both the type name and the member layout appear to be jointly necessary
for the conflation to occur:
- same name, same layout -> FAILS (conflated)
- same name, different layout -> compiles fine
- different name, same layout -> compiles fine
- namespace-scope (non-local) types -> compiles fine regardless of
name/layout
- a nested type of a namespace-scope class -> compiles fine
This is not specific to any particular library, to GoogleTest, or to any
other test framework: it reproduces standalone with only `<meta>` and no
other dependency, in a bare `main()`.
## Minimal reproduction
#include <cstddef>
#include <iostream>
#include <meta>
#include <string>
#include <string_view>
#include <type_traits>
template <typename T> struct MembersOf {
static constexpr auto value =
std::define_static_array(
std::meta::nonstatic_data_members_of(^^T,
std::meta::access_context::current()));
};
template <typename T, std::size_t I, std::size_t N>
void dump(const T &obj, std::string &out) {
if constexpr (I < N) {
constexpr auto member = MembersOf<T>::value[I];
constexpr auto name =
std::define_static_string(std::meta::identifier_of(member));
out += std::string_view(name);
out += "=";
const auto &v = obj.[:member:];
using D = std::decay_t<decltype(v)>;
if constexpr (std::is_same_v<D, std::string>) {
out += v;
} else if constexpr (std::is_arithmetic_v<D>) {
out += std::to_string(v);
} else {
out += "?";
}
out += " ";
dump<T, I + 1, N>(obj, out);
}
}
template <typename T> std::string render(const T &obj) {
std::string out;
dump<T, 0, MembersOf<T>::value.size()>(obj, out);
return out;
}
void f1() {
struct Local {
std::string firstname;
std::string lastname;
unsigned int age;
};
Local a{"Albert", "Londre", 65};
std::cout << render(a) << "\n";
}
void f2() {
struct Local { // same name, same layout as f1()::Local,
std::string firstname; // but a completely distinct, unrelated
type
std::string lastname;
unsigned int age;
};
Local b{"Romain", "Garry", 53};
std::cout << render(b) << "\n";
}
int main() {
f1();
f2();
}
Build:
g++ \
-std=c++26 \
-freflection \
-Wall \
-Wextra \
-pedantic-errors \
-save-temps \
-c repro.cpp
## Expected behavior
`f1()::Local` and `f2()::Local` are two unrelated types that happen to
share a spelling and a member layout. Both calls to `render()` should
compile and each should print its own object's fields.
## Actual behavior
repro.cpp: In instantiation of 'void dump(const T&, std::string&)
[with T = f2()::Local; long unsigned int I = 0; long unsigned int N = 3;
std::string = std::__cxx11::basic_string<char>]':
repro.cpp:37:43: required from 'std::string render(const T&)
[with T = f2()::Local; ...]'
repro.cpp:58:24: required from here
repro.cpp:20:37: error: invalid use of non-static data member
'f1()::Local::firstname'
20 | const auto &v = obj.[:member:];
| ~~~~~~~~~~~~^~
Note that the diagnostic is instantiating for `T = f2()::Local` but names a
member of `f1()::Local` — the type from the *other*, unrelated local scope.
## Testable hypothesis
`std::define_static_array` and `std::define_static_string` materialize a
compile-time-computed value into an object of static storage duration.
Their purpose plausibly includes interning/deduplicating identical results
to avoid redundant static objects.
The behavior suggests that the identity used to materialize/deduplicate
the result of define_static_array / define_static_string does not fully
distinguish local class declarations that have identical names and layouts.
The observed dependency on both the local class name and its member layout
suggests that some internal identity or canonicalization mechanism may be
conflating reflection metadata for distinct local class declarations.
define_static_array / define_static_string are suspected because removing
either materialization step changes the behavior.
## Additional observation
The conflation also occurs across differing `access_context` arguments:
we independently observed the identical symptom (same diagnostic shape,
member from one local class attributed to the instantiation of another)
when one of the two homonymous, same-layout local classes was reflected
via `std::meta::access_context::current()` and the other via
`std::meta::access_context::unchecked()`. This rules out `access_context`
as a contributing factor.
## Isolation notes
This was originally noticed while adding aggregate auto-serialization to a
small logging library, where two locally-scoped structs named `User` in
different unit tests collided. The four-way isolation above (name x
layout) was done by systematically varying one axis at a time; happy to
share the intermediate probes if useful.