The exact mechanism of #290, one concept down: convertible_to_optional and convertible_to_choice (include/fn/concepts.hpp) instantiate optional<void> / choice<void> to answer for T = void, and both classes refuse the instantiation with a class-body mandate — which fires outside any immediate context, so no enclosing requires-expression can absorb it.
convertible_to_optional is user-reachable. The optional arm of invocable_transform asks it about the callback's result:
#include <fn/transform.hpp>
struct Error final {};
constexpr auto fnVoid = [](auto &&...) -> void {};
static_assert(not fn::invocable_transform<decltype(fnVoid), fn::optional<Error>>);
hard-errors on g++ and clang, and so does o | transform([](Error) {}) — where the honest answer is false: std::optional::transform mandates a non-void result. (Contrast expected, where a void return is legitimate — transform yields expected<void, E> — and convertible_to_expected carries an explicit void arm for exactly that reason.)
convertible_to_choice is not reached by any verb, but it is a public concept, and asking must answer:
#include <fn/concepts.hpp>
static_assert(not fn::convertible_to_optional<void>);
static_assert(not fn::convertible_to_choice<void>);
Both assertions are hard errors today.
Fix direction: the void conjunct #290 places on convertible_to_unexpected, applied to both — for neither concept can void ever be valid, so each just needs to say so.
Reproduced at 58d131c with gcc 16.1.1 and clang 22.1.6 (x86-64 Linux).
The exact mechanism of #290, one concept down:
convertible_to_optionalandconvertible_to_choice(include/fn/concepts.hpp) instantiateoptional<void>/choice<void>to answer forT= void, and both classes refuse the instantiation with a class-body mandate — which fires outside any immediate context, so no enclosing requires-expression can absorb it.convertible_to_optionalis user-reachable. The optional arm ofinvocable_transformasks it about the callback's result:hard-errors on g++ and clang, and so does
o | transform([](Error) {})— where the honest answer isfalse:std::optional::transformmandates a non-void result. (Contrastexpected, where a void return is legitimate —transformyieldsexpected<void, E>— andconvertible_to_expectedcarries an explicit void arm for exactly that reason.)convertible_to_choiceis not reached by any verb, but it is a public concept, and asking must answer:Both assertions are hard errors today.
Fix direction: the void conjunct #290 places on
convertible_to_unexpected, applied to both — for neither concept can void ever be valid, so each just needs to say so.Reproduced at 58d131c with gcc 16.1.1 and clang 22.1.6 (x86-64 Linux).