Skip to content

Fix reflection of non-default-constructible and empty aggregates - #30

Merged
Yaraslaut merged 1 commit into
masterfrom
fix/review-findings
Oct 1, 2026
Merged

Yaraslaut merged 1 commit into
masterfrom
fix/review-findings

Conversation

@Yaraslaut

Copy link
Copy Markdown
Member

#29 taught CountMembers to count aggregates that have a member without a default constructor, but the rest of the library still could not handle them: MemberTypeOf created an Object {} to look at its members, and CallOnMembers, FoldMembers, CollectDifferences and the type-only EnumerateMembers all go through it. This is the second blocker described in LASTRADA-Software/Lightweight#50. The other changes come out of a review of the whole header for bugs and accidental copies.

Changes

  • Non-default-constructible aggregates. MemberTypeOf uses std::declval instead of Object {}. A test covers the shape from the Lightweight issue: a field type with a deleted default constructor and a greedy variadic constructor template.
  • Empty aggregates. CallOnMembers, FoldMembers and Inspect rejected aggregates without members, because their constraints ask for the type of the first member.
  • Inspect failed to compile for members that are enums, optionals, ranges, or anything else that is neither a string, a number nor an aggregate. Enums are now shown by value, optionals by their value or nullopt, ranges as [a, b], a null char const* as null, and anything that cannot be formatted by its type name in angle brackets. Output for the previously supported types is unchanged. It also appends to the result directly rather than formatting each member into a temporary string.
  • FoldMembers copied the initial value and passed the accumulator to the callable as an lvalue, which cost one copy per member for callables taking it by value. Both are moved now, so move-only accumulators work. A callable taking the accumulator as a non-const lvalue reference no longer compiles.
  • CallOnMembersWithoutName passed a reference type (int&) as the member type; it is the plain member type now, as in MemberTypeOf.
  • TypeNameOf truncated names such as int[3], and on MSVC removed everything before the first space, so unsigned int became int. Only a leading class-key is removed there now.
  • ToTuple uses a structured binding pack where the compiler provides one, so the chain of 150 branches is skipped by the preprocessor. Parsing the header beyond its standard includes drops from roughly 118 ms to 11 ms on gcc and from 37 ms to 13 ms on clang.
  • <concepts> is included directly, and ToTuple / GetMemberAt document that their result refers into the argument.

A copy-counting member type confirmed that ToTuple, GetMemberAt, EnumerateMembers, CallOnMembers, CallOnMembersWithoutName and CollectDifferences make no copies or moves of members.

Current Lightweight master builds against this branch without errors or warnings.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ar6Ahymw9LhLsnM324z5cV

MemberTypeOf created an `Object {}` to look at its members, so it needed a
default constructor. CallOnMembers, FoldMembers, CollectDifferences and the
type-only EnumerateMembers all go through it, and therefore failed to
compile for exactly the aggregates CountMembers learned to count recently.
It uses std::declval now.

Also fixed along the way:

- CallOnMembers, FoldMembers and Inspect rejected aggregates without
  members, because their constraints ask for the type of the first member.
- Inspect failed to compile for members that are enums, optionals, ranges
  or of any other type that is neither a string, a number nor an aggregate.
  Enums are shown by value, optionals by their value or `nullopt`, ranges
  as `[a, b]`, and anything else that cannot be formatted by its type name.
  It also appends to the result directly instead of formatting every member
  into a temporary string first.
- FoldMembers copied the initial value, and handed the accumulator to the
  callable as an lvalue, costing a copy per member for callables taking it
  by value. Both are moved now, which makes move-only accumulators usable.
- CallOnMembersWithoutName passed a reference type as the member type. It
  is the plain member type now, as in MemberTypeOf.
- TypeNameOf truncated names containing the character sequence that ends
  the name in the compiler's function signature (e.g. `int[3]`), and on
  MSVC cut everything before the first space, so that `unsigned int` became
  `int`. Only a leading class-key is removed there now.
- ToTuple uses a structured binding pack where available, instead of the
  chain of 150 branches, which the compiler then no longer has to parse.
- <concepts> is used by the header, but was only included transitively.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ar6Ahymw9LhLsnM324z5cV
Signed-off-by: Yaraslau Tamashevich <yaraslau.tamashevich@gmail.com>
@Yaraslaut
Yaraslaut merged commit e401817 into master Oct 1, 2026
33 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant