Give a copack with exactly one alternative the full tuple protocol, complementing the existing index-less get(cp):
std::tuple_size (= 1)
std::tuple_element
- ADL
get<0> in all cv/ref-qualified forms
- Structured bindings
The protocol remains restricted to singular copacks:
- A multi-alternative copack is unchanged. Its active alternative is a runtime property, so
tuple_size and tuple_element have no static answer—the same reason the index-less get is already singular-only.
copack<> is explicitly refused. Setting tuple_size == 0 would make the uninhabited zero resemble the unit pack<>, creating exactly the zero/unit conflation that the algebra separates.
Motivation: Generic Interoperability
A singular copack becomes usable wherever protocol-generic code accepts a one-element tuple. The state-shape isomorphism singular copack ≅ one-element pack ≅ payload becomes exercisable in generic code, while every nominal distinction remains intact.
The protocol adds no type identity: copack<X>, pack<X>, and X remain distinct types; normalization and deduplication are untouched; and copack_for<A, pack<A>, std::tuple<A>, std::array<A,1>>::size remains 4. The algebra is not weakened.
Doctrine: Storage and Call Views May Differ in Arity
This difference is intentional. For copack<pack<A,B>>, tuple_size is 1 and get<0> yields the whole pack, while apply eliminates at arity 2. The two protocols answer different questions:
get<0> asks: Which alternative is stored?
apply asks: What argument list does it make?
This is the converse of the existing "Storage shape vs call shape" principle: different types, same call shape. It should be added to that section.
This is not the rejected nested-pack incoherence. In that case, a canonical flat duplicate of the same product existed, making the disagreement gratuitous. Here, no flat alternative spelling exists: pack<A,B> alone denotes a product, not a sum of one. Moreover, the storage view is genuinely inexpressible through apply, because the arity-1 whole-pack call does not compile.
No Enrolment in apply's Tuple-Like Arm
By declared doctrine, the unpacked domain of pfn::apply and fn::apply is the enumerative standard set: array, complex, pair, tuple, and subrange. Acquiring the tuple protocol therefore does not divert fn::apply; the sum arm continues to win. pfn::apply continues to refuse copacks, preserving fn ⊇ pfn.
Implementation Rider
Verify whether C++26 std::apply is constrained by the tuple-like concept or specified through tuple_size and get. If the latter, std::apply would accept a singular copack through its storage view while pfn::apply refuses it. That raises a pfn fidelity question to settle, separately if necessary.
Documentation
Update TYPE_ALGEBRA.md §4 on singular extraction, the "Storage shape vs call shape" section, and add a dated CHANGELOG.md entry.
Assisted-by: Claude:claude-fable-5
Give a
copackwith exactly one alternative the full tuple protocol, complementing the existing index-lessget(cp):std::tuple_size(= 1)std::tuple_elementget<0>in all cv/ref-qualified formsThe protocol remains restricted to singular copacks:
tuple_sizeandtuple_elementhave no static answer—the same reason the index-lessgetis already singular-only.copack<>is explicitly refused. Settingtuple_size == 0would make the uninhabited zero resemble the unitpack<>, creating exactly the zero/unit conflation that the algebra separates.Motivation: Generic Interoperability
A singular copack becomes usable wherever protocol-generic code accepts a one-element tuple. The state-shape isomorphism singular copack ≅ one-element pack ≅ payload becomes exercisable in generic code, while every nominal distinction remains intact.
The protocol adds no type identity:
copack<X>,pack<X>, andXremain distinct types; normalization and deduplication are untouched; andcopack_for<A, pack<A>, std::tuple<A>, std::array<A,1>>::sizeremains 4. The algebra is not weakened.Doctrine: Storage and Call Views May Differ in Arity
This difference is intentional. For
copack<pack<A,B>>,tuple_sizeis 1 andget<0>yields the whole pack, whileapplyeliminates at arity 2. The two protocols answer different questions:get<0>asks: Which alternative is stored?applyasks: What argument list does it make?This is the converse of the existing "Storage shape vs call shape" principle: different types, same call shape. It should be added to that section.
This is not the rejected nested-
packincoherence. In that case, a canonical flat duplicate of the same product existed, making the disagreement gratuitous. Here, no flat alternative spelling exists:pack<A,B>alone denotes a product, not a sum of one. Moreover, the storage view is genuinely inexpressible throughapply, because the arity-1 whole-pack call does not compile.No Enrolment in
apply's Tuple-Like ArmBy declared doctrine, the unpacked domain of
pfn::applyandfn::applyis the enumerative standard set: array, complex, pair, tuple, and subrange. Acquiring the tuple protocol therefore does not divertfn::apply; the sum arm continues to win.pfn::applycontinues to refuse copacks, preservingfn⊇pfn.Implementation Rider
Verify whether C++26
std::applyis constrained by the tuple-like concept or specified throughtuple_sizeandget. If the latter,std::applywould accept a singular copack through its storage view whilepfn::applyrefuses it. That raises apfnfidelity question to settle, separately if necessary.Documentation
Update TYPE_ALGEBRA.md §4 on singular extraction, the "Storage shape vs call shape" section, and add a dated CHANGELOG.md entry.
Assisted-by: Claude:claude-fable-5