Constrain the relocating verbs on what they actually relocate#303
Conversation
🤖 Augment PR SummarySummary: Fixes SFINAE for “relocating” verbs ( Changes:
Technical Notes: The key idea is to ask “can the body build what it actually builds (and can it throw)?” rather than “is the type move-constructible?”, which would incorrectly accept move-only payloads piped as lvalues. Closes #250. 🤖 Was this summary useful? React with 👍 or 👎 |
d8c418d to
ebc910e
Compare
f1df6ea to
34cda15
Compare
ebc910e to
661392a
Compare
`value_or`, `recover`, `fail` and `filter` each return a FRESH monad, carrying the untouched side of the operand into it. Their concepts never asked whether that was possible, so an immovable value or error satisfied them and then hard-errored deep inside the body - the failure #250 was raised for, where a clean SFINAE drop was owed. The question is not `is_move_constructible_v`. The body relocates in whatever value category the operand is piped in - an lvalue is copied, an rvalue moved - so a move-only value piped as an lvalue must be REJECTED, and that trait would accept it. `_relocatable_value` / `_relocatable_error` / `_relocatable` ask what the bodies construct, which is also what their noexcept specs weigh: one asks "can it", the other "can it throw". The same lie had a second trigger. An `optional<T&>` binds its referent rather than carrying it, and its value type is the referent - so `is_constructible_v< value_type, Args...>` said yes to `optional<int&> | value_or(1)`, and the body then could not bind `int&` to the prvalue fallback. The fallback builds the RESULT, so that is what the concept now asks; an lvalue fallback still works, and an immovable referent stays pipeable, because nothing is relocated through a reference. Closes #250 Assisted-by: Claude:claude-opus-4-8[1m]
The specification restated the `or_else` delegation with the whole-monad question - `is_nothrow_constructible_v<type, V>` - which weighs the error's copy, though no branch of the body relocates the error: the value branch carries the value into the result and the error branch builds the fallback. So `value_or` reported `noexcept(false)` for a nothrow value beside a throwing-copy error. The conjunct now asks the value relocation the body performs, as `recover`, `fail` and `filter` already do. Assisted-by: Claude:claude-fable-5
661392a to
19eaeaf
Compare
|
augment review |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
Take #303's relocation constraints and #304's sum assignment - both additive, so the reconciliation is placement rather than flips: the constraints cases join the verb files, the assignment cases sum and choice, and expected gains the graded shape that could not be assigned at all before. One assertion of ours had to go the other way. #303 asks what value_or actually relocates, and it is the value alone - the error is discarded, never carried into the result - so its throwing copy no longer weighs, and the comment claiming it did described the old imprecision as if it were the design. Assisted-by: Claude:claude-opus-4-8[1m]
Take #303's relocation constraints and #304's sum assignment - both additive, so the reconciliation is placement rather than flips: the constraints cases join the verb files, the assignment cases sum and choice, and expected gains the graded shape that could not be assigned at all before. One assertion of ours had to go the other way. #303 asks what value_or actually relocates, and it is the value alone - the error is discarded, never carried into the result - so its throwing copy no longer weighs, and the comment claiming it did described the old imprecision as if it were the design. Assisted-by: Claude:claude-opus-4-8[1m]



value_or,recover,failandfiltereach return a FRESH monad, carrying the untouched side of the operand into it. Their concepts never asked whether that was possible, so an immovable value or error satisfied them and then hard-errored deep inside the body - the failure #250 was raised for, where a clean SFINAE drop was owed.The question is not
is_move_constructible_v. The body relocates in whatever value category the operand is piped in - an lvalue is copied, an rvalue moved - so a move-only value piped as an lvalue must be REJECTED, and that trait would accept it._relocatable_value/_relocatable_error/_relocatableask what the bodies construct, which is also what their noexcept specs weigh: one asks "can it", the other "can it throw".The same lie had a second trigger. An
optional<T&>binds its referent rather than carrying it, and its value type is the referent - sois_constructible_v<value_type, Args...>said yes tooptional<int&> | value_or(1), and the body then could not bindint&to the prvalue fallback. The fallback builds the RESULT, so that is what the concept now asks; an lvalue fallback still works, and an immovable referent stays pipeable, because nothing is relocated through a reference.Closes #250
Stack: P9 of 13 — the release-0.1 bugfix queue, merging bottom-up into
main. Based on #302 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).