Problem
A functor that returns a fresh monad by value must relocate whatever it carries through, so the carried side must be move-constructible. The invocable_* concepts don't require this, so an immovable value/error type satisfies the concept and then hard-errors deep in the body (e.g. an or_else substitution failure) instead of cleanly SFINAE-rejecting.
Surfaced while adding value_or tests: fn::expected<Immovable, E> | value_or(args...) reports as invocable, then fails to compile.
Affected functors (verbs)
| Functor |
Relocates |
Needs movable |
value_or |
existing value (success branch) |
value |
recover |
existing value (success branch) |
value |
fail |
existing error (error branch) |
error |
filter |
whole monad via return FWD(v) |
value + error |
and_then / transform / or_else / transform_error delegate to the expected member and likely reject transitively (to confirm). inspect / inspect_error return V&& (no relocation) and discard returns void — none needed.
Proposal
Add an explicit movability requirement to the relevant invocable_* concepts so immovable types are SFINAE-rejected, plus negative invocability tests documenting the boundary.
Open question: simple std::is_move_constructible_v<T> vs. a value-category-precise std::is_constructible_v<T, decltype(carried)>.
Assisted-by: Claude:claude-opus-4-8
Problem
A functor that returns a fresh monad by value must relocate whatever it carries through, so the carried side must be move-constructible. The
invocable_*concepts don't require this, so an immovable value/error type satisfies the concept and then hard-errors deep in the body (e.g. anor_elsesubstitution failure) instead of cleanly SFINAE-rejecting.Surfaced while adding
value_ortests:fn::expected<Immovable, E> | value_or(args...)reports as invocable, then fails to compile.Affected functors (verbs)
value_orrecoverfailfilterreturn FWD(v)and_then/transform/or_else/transform_errordelegate to theexpectedmember and likely reject transitively (to confirm).inspect/inspect_errorreturnV&&(no relocation) anddiscardreturns void — none needed.Proposal
Add an explicit movability requirement to the relevant
invocable_*concepts so immovable types are SFINAE-rejected, plus negative invocability tests documenting the boundary.Open question: simple
std::is_move_constructible_v<T>vs. a value-category-precisestd::is_constructible_v<T, decltype(carried)>.Assisted-by: Claude:claude-opus-4-8