Authors: Viacheslav Luchkin, Gašper Ažman
Document: P3822R1
Date: 2026-02-14
Target: EWG → CWG
Link: wg21.link/p3822r1
You know how function declarations have had noexcept(expr) since C++11? Turns out you can't do that in a compound requirement. You get bare noexcept or nothing. So if you need to conditionally check whether an expression is non-throwing - say, in a type-erased wrapper parameterized on noexcept - you have to duplicate the entire concept body with a ternary: one branch that checks noexcept, one that doesn't.
P3822 extends compound requirements to accept noexcept(constant-expression), filling the gap. Small change, reuses existing grammar from [except.spec], has a Clang implementation. EWG forwarded it to CWG for C++29.
love that we're three decades into C++ and still finding grammar cells that
noexceptforgot to colonizeWait -
noexcept(expr)has been legal on function declarations since C++11 but not in compound requirements? I genuinely assumed this already worked. Who discovered this gap?Anyone writing type-erased wrappers parameterized on noexcept. The workaround is a ternary concept that duplicates the entire body:
It works but it's the kind of thing that makes you wonder if the language is trolling you. The paper's type erasure example on Godbolt is the more complete motivating case.
Small paper but the inconsistency it fixes is real. The proposed wording reuses the existing
noexcept-specifiergrammar from [except.spec] verbatim - no new production rules, just plumbing the existing one into compound requirements where it should have been all along.EWG forwarded to CWG for C++29: SF 10, F 16, N 4, A 4, SA 0. Four against is a little surprising for something this mechanical. Maybe concern about the grammar surface area? Or just people who don't think the ternary workaround is painful enough to justify a language change.
There's a Clang fork with a working implementation. Apparently most of the existing exception specification logic was reusable, which is kind of the point.
One design detail worth noting: if the constant-expression fails to convert to
bool, the requirement is not satisfied rather than a hard error. Same reasoning as SFINAE - you want overload resolution to keep looking, not detonate.Given that
noexcept(true)andnoexcept(false)are the only realistic values, it's hard to see how you'd actually trigger this in practice. But it's a nice safety net for heavily-templated constraint machinery where someone substitutes a type that doesn't even have a viable conversion.I've hit exactly this writing a
move_only_functionalternative at work. Ended up with two separate concepts and arequiresclause that looked like it was generated by a macro. Would genuinely love to just writenoexcept(noexc)in the compound requirement and be done with it.Honest question: does anyone actually get
noexceptright in generic code? Every time I try I end up in anoexcept(noexcept(noexcept(...)))hall of mirrors and give up.At scale, yes. Anything that does type erasure or wraps callables needs to propagate it.
std::functiondoesn't bother butstd::move_only_functiondoes, and that distinction matters in performance-sensitive code.