Constrain in_place_type construction of sum and choice on the argument list#296
Merged
Conversation
This was referenced Jul 13, 2026
🤖 Augment PR SummarySummary: This PR makes Changes:
🤖 Was this summary useful? React with 👍 or 👎 |
…rgument list
The constructors checked only `has_type<T>`, so any argument list was reported
viable and a bad one then failed to compile inside `variadic_union`, outside the
immediate context. They now also require that the alternative can be constructed
from the arguments, as `pack::append` already did; `choice`'s constructor
forwards to `sum`'s and shared the gap.
The conjunct is brace-initializability, not `is_constructible_v`: the storage
constructs the element as `T{args...}`, which elides braces for an aggregate
(`std::array<int, 3>` is not `is_constructible_v` from three ints, yet its
forwarding construction is supported and tested) and rejects narrowing.
`as_sum` gets the same conjunct on its `in_place_type` overload, and the guard
`as_pack` already carried on its value lift - without it, a tag whose type is not
default-constructible fell through to the value overload and was wrapped as an
alternative.
Closes #284
Assisted-by: Claude:claude-opus-4-8[1m]
`append` constrained on `is_constructible_v` while `_append` brace-initializes the
element, so an aggregate could not be appended element-wise (`std::array<int, 3>`
is not `is_constructible_v` from three ints) though `sum` constructs exactly that,
and a narrowing argument list was admitted for the body to reject. Both now use the
constraint introduced for `sum`, which asks about the initialization actually
performed.
That constraint grows a reference leg in the process: a reference element is bound,
not brace-initialized, and `T{...}` for a reference `T` is not a portable question -
gcc rejects it, clang accepts - so it must be spelled as the binding it is. Renamed
`_brace_constructible` to `_initializable` accordingly.
Assisted-by: Claude:claude-opus-4-8[1m]
`_append` built every element as `T{FWD(args)...}`. For a reference `T` that is a
bind ([expr.type.conv]/1.3 defines it as direct-initializing an invented variable
of type T, and [dcl.init.list]/3.9 initializes a reference from a single element
whose type is reference-related to it), but gcc reads the braced functional-cast
form as materializing a temporary and rejects the binding - while accepting the
equivalent declaration. So appending an lvalue, which deduces a reference element,
did not compile on gcc at all.
It went unnoticed because every test of such an append only took `decltype(...)` of
the call, never instantiating the body. The new tests evaluate it, and assert the
element binds - address identity, and mutation observed through the pack - with a
constexpr twin.
Closes #287
Assisted-by: Claude:claude-opus-4-8[1m]
ad37ed3 to
a8019a3
Compare
Member
Author
|
augment review |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
Member
Author
|
augment review |
Bronek
added a commit
that referenced
this pull request
Jul 13, 2026
Take #296's corrected expectations for sum, choice and pack. The in_place_type constraints flip #284's tripwires, and the initializability conjunct brings probes this suite could not state before: aggregate brace-init, narrowing rejection, and reference elements bound rather than brace-initialized. Main's new blocks land as SECTIONs, keeping the suite free of the BDD macros. The #280 noexcept tripwires stay - that issue is still open. Assisted-by: Claude:claude-opus-4-8[1m]
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Three interdependent fixes to the question "can this element be constructed": the brace-initializability trait is introduced for
sumandchoice, adopted and renamed forpack, andpack's reference elements are bound to match.Constrain
in_place_typeconstruction ofsumandchoiceon the argument listThe constructors checked only
has_type<T>, so any argument list was reported viable and a bad one then failed to compile insidevariadic_union, outside the immediate context. They now also require that the alternative can be constructed from the arguments, aspack::appendalready did;choice's constructor forwards tosum's and shared the gap.The conjunct is brace-initializability, not
is_constructible_v: the storage constructs the element asT{args...}, which elides braces for an aggregate (std::array<int, 3>is notis_constructible_vfrom three ints, yet its forwarding construction is supported and tested) and rejects narrowing.as_sumgets the same conjunct on itsin_place_typeoverload, and the guardas_packalready carried on its value lift - without it, a tag whose type is not default-constructible fell through to the value overload and was wrapped as an alternative.Ask of a
packelement the same question its initialization answersappendconstrained onis_constructible_vwhile_appendbrace-initializes the element, so an aggregate could not be appended element-wise (std::array<int, 3>is notis_constructible_vfrom three ints) thoughsumconstructs exactly that, and a narrowing argument list was admitted for the body to reject. Both now use the constraint introduced forsum, which asks about the initialization actually performed.That constraint grows a reference leg in the process: a reference element is bound, not brace-initialized, and
T{...}for a referenceTis not a portable question - gcc rejects it, clang accepts - so it must be spelled as the binding it is. Renamed_brace_constructibleto_initializableaccordingly.Bind a reference
packelement instead of brace-initializing it_appendbuilt every element asT{FWD(args)...}. For a referenceTthat is a bind ([expr.type.conv]/1.3 defines it as direct-initializing an invented variable of type T, and [dcl.init.list]/3.9 initializes a reference from a single element whose type is reference-related to it), but gcc reads the braced functional-cast form as materializing a temporary and rejects the binding - while accepting the equivalent declaration. So appending an lvalue, which deduces a reference element, did not compile on gcc at all.It went unnoticed because every test of such an append only took
decltype(...)of the call, never instantiating the body. The new tests evaluate it, and assert the element binds - address identity, and mutation observed through the pack - with a constexpr twin.Closes #284
Closes #287
Stack: P2 of 13 — the release-0.1 bugfix queue, merging bottom-up into
main. Based on #295 and to be retargeted tomainonce it merges; the diff shows only this PR's commits. Every stack boundary was validated with a gcc build and the full test suite on top ofmain, and the aggregate branch ran the full CI matrix green (#289).