fn::invocable_transform_error with a void-returning callback is a hard compile error rather than false. The first disjunct asks
{ ::fn::invoke(FWD(fn), FWD(v).error()) } -> convertible_to_unexpected;
and convertible_to_unexpected<void> (include/fn/concepts.hpp:72) instantiates pfn::unexpected<void>, whose validity mandate is a class-body static_assert (include/pfn/expected.hpp:125) — it fires during instantiation, outside any immediate context, so no enclosing requires-expression can absorb it.
Consequence for user code: e | transform_error([](Error) {}) hard-errors where every sibling verb SFINAE-drops an unusable callback. The contrast with transform is sharp: there a void return is legitimate (it transforms the value side to expected<void, E>), and convertible_to_expected (concepts.hpp:82) carries an explicit same_as<T, void> arm for exactly that reason. convertible_to_unexpected has no such guard, and for it void can never be valid — the concept just needs to say so instead of exploding.
Nine-line repro, verified on g++ 14.3.0 and clang++ 16.0.6 — identical failure, both naming the unexpected<void> mandate (g++ follows up with forming reference to void from the void instantiation's error() const &):
#include <fn/transform_error.hpp>
struct Error final {};
constexpr auto fnVoid = [](Error) -> void {};
static_assert(not fn::invocable_transform_error<decltype(fnVoid), fn::expected<int, Error>>);
int main() { return 0; }
Fix direction: mirror convertible_to_expected's void handling — e.g. concept convertible_to_unexpected = (not ::std::is_void_v<::std::remove_cvref_t<T>>) && requires { ... }; — one conjunct in concepts.hpp, after which the repro's static_assert holds.
Same class as #277 (validity surfacing where it hard-errors instead of in a constraint that yields false), though the mechanism here is a mandate inside a concept rather than a deduced-return body. Found while probing the verb concepts on the #85 branch; tests/fn/transform_error.cpp carries a GAP comment at the probe site that cites this issue and gains a real negative probe once fixed.
fn::invocable_transform_errorwith a void-returning callback is a hard compile error rather thanfalse. The first disjunct asks{ ::fn::invoke(FWD(fn), FWD(v).error()) } -> convertible_to_unexpected;and
convertible_to_unexpected<void>(include/fn/concepts.hpp:72) instantiatespfn::unexpected<void>, whose validity mandate is a class-bodystatic_assert(include/pfn/expected.hpp:125) — it fires during instantiation, outside any immediate context, so no enclosing requires-expression can absorb it.Consequence for user code:
e | transform_error([](Error) {})hard-errors where every sibling verb SFINAE-drops an unusable callback. The contrast withtransformis sharp: there a void return is legitimate (it transforms the value side toexpected<void, E>), andconvertible_to_expected(concepts.hpp:82) carries an explicitsame_as<T, void>arm for exactly that reason.convertible_to_unexpectedhas no such guard, and for it void can never be valid — the concept just needs to say so instead of exploding.Nine-line repro, verified on g++ 14.3.0 and clang++ 16.0.6 — identical failure, both naming the
unexpected<void>mandate (g++ follows up withforming reference to voidfrom the void instantiation'serror() const &):Fix direction: mirror
convertible_to_expected's void handling — e.g.concept convertible_to_unexpected = (not ::std::is_void_v<::std::remove_cvref_t<T>>) && requires { ... };— one conjunct inconcepts.hpp, after which the repro'sstatic_assertholds.Same class as #277 (validity surfacing where it hard-errors instead of in a constraint that yields false), though the mechanism here is a mandate inside a concept rather than a deduced-return body. Found while probing the verb concepts on the #85 branch;
tests/fn/transform_error.cppcarries a GAP comment at the probe site that cites this issue and gains a real negative probe once fixed.