https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127284
Bug ID: 127284
Summary: [c++26][contracts] a lambda in a non-member function's
postcondition fails to parse when there is a
result-name-introducer
Product: gcc
Version: 16.2.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: c++
Assignee: unassigned at gcc dot gnu.org
Reporter: berne at notadragon dot com
Target Milestone: ---
Created attachment 65533
--> https://gcc.gnu.org/bugzilla/attachment.cgi?id=65533&action=edit
Parse failure on the free-function form, with the accepted controls beside it
A lambda-expression in the predicate of a non-member function's
postcondition does not parse when the postcondition has a
result-name-introducer.
```
bool ok() { return true; }
// (1) THE BUG: free function, postcondition, result-name-introducer.
int broken(int x) post(r : [] { return ok(); }()) { return x; }
// (2) Control: free function, postcondition, NO result name. Accepted.
int no_result_name(int x) post([] { return ok(); }()) { return x; }
// (3) Control: free function, precondition. Accepted.
int precondition(int x) pre([] { return ok(); }()) { return x; }
// (4) Control: member function, postcondition, result-name-introducer.
// Version-dependent, which is why it is commented out: 16.1.0 and
// 16.2.0 reject this too, with a DIFFERENT diagnostic ("expected
// conditional-expression"), while trunk accepts it. The member-function
// half was fixed by PR c++/125537 (r17-3710, commit 943089ca86f), which
// narrowed the same raise at the other of its two sites; the
// free-function half, row (1), was not. Uncomment to see whichever
// your compiler does.
//
// struct S {
// int mem(int x) post(r : [] { return ok(); }()) { return x; }
// };
int main() { }
```
```
lambda-in-postcondition-result-name.cpp:22:30: error: expected ')' before '{'
token
22 | int broken(int x) post(r : [] { return ok(); }()) { return x; }
| ~ ^~
| )
lambda-in-postcondition-result-name.cpp:22:48: error: expected unqualified-id
before ')' token
22 | int broken(int x) post(r : [] { return ok(); }()) { return x; }
| ^
```
CONTROLS
Neither postconditions nor lambdas in predicates are broken on their own; it
takes the combination. Measured 2026-09-09:
case 16.2.0 trunk
free fn, post(r : []{...}()), result name REJECTED REJECTED
free fn, post([]{...}()), no result name accepted accepted
free fn, pre([]{...}()) accepted accepted
member fn, post(r : []{...}()), result name REJECTED accepted
The last row is worth stating plainly: on 16.x the member-function form is
also rejected, with a DIFFERENT diagnostic ("expected
conditional-expression"), and trunk accepts it. So the member half is
already fixed upstream and the free-function half is not. A member's
contract is deferred and late-parsed once the class is complete, whereas a
free function's is parsed straight off the declarator, which is why the two
halves can diverge at all.
DISCOVERY
Found while implementing capture of enclosing entities by a lambda written
inside a contract predicate ([expr.prim.lambda.capture]/3.3, PR117435). It
surfaced as a spurious failure in that work's own regression test and was
isolated out of it; no capture is needed to provoke it.
ANALYSIS
The non-deferred contract path raises processing_template_decl whenever a
result-name-introducer is present, because the result variable is typed with
make_auto () -- the return type is not available while the predicate is
parsed off the declarator -- so the predicate is treated as dependent.
Lambda parsing does not cope with that flag being raised outside a real
template; that is PR99546, and cp_parser_lambda_expression already carries a
workaround for it, gated on current_binding_level->requires_expression.
That accounts for every row above: postconditions only, because only they
have a result name; non-members only, because a member's late-parsed path
raises the flag only for an undeduced return type; and the lambda's position
within the predicate is irrelevant, the flag being global for the parse.
That asymmetry is recent and traceable. r17-3710, commit 943089ca86f,
"c++/contracts: unify condition for pseudo-template mode" [PR c++/125537],
made the raise in cp_parser_late_contract_condition -- the member path --
conditional on the result binding having an undeduced return type, which is
why the member row changed between 16.2.0 and trunk. The non-deferred site
in cp_parser_function_contract_specifier still raises whenever a result
identifier is present, so the free-function row did not change. This is the
same defect at the site that fix did not reach: not a regression from it,
and not a duplicate of 125537, which described a different symptom.
Extending that gate to the contract scope is half of a fix. The other half
is needed because the two constructs differ in a way the workaround's own
comment does not mention: a requires-expression's operand is never
substituted afterwards, so a non-templatey lambda built there is never
looked at again, whereas a contract predicate on this path IS substituted --
by rebuild_postconditions, once the return type is known. tsubst_lambda_expr
then asks tsubst_function_decl to substitute into an operator() with no
template info, which is what that function opens by asserting it is never
asked to do.
VERSIONS -- all on x86_64-linux-gnu
source version free fn w/ result name
compiler-explorer 16.1.0 rejected
compiler-explorer 16.2.0 rejected
compiler-explorer 17.0.0 20260909, 919c0d16c91 rejected
local build -g 17.0.0 20260909, 7dab38c9d71 rejected
```
$ ./gcc-16.2.0/bin/g++ -v
Using built-in specs.
COLLECT_GCC=./gcc-16.2.0/bin/g++
COLLECT_LTO_WRAPPER=/home/jberne4/repos/compilers/gcc-16.2.0/bin/../libexec/gcc/x86_64-linux-gnu/16.2.0/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../gcc-16.2.0/configure
--prefix=/opt/compiler-explorer/gcc-build/staging --build=x86_64-linux-gnu
--host=x86_64-linux-gnu --target=x86_64-linux-gnu --disable-bootstrap
--enable-multiarch --with-abi=m64 --with-multilib-list=m32,m64,mx32
--enable-multilib --enable-clocale=gnu
--enable-languages=c,c++,fortran,ada,objc,obj-c++,go,d,m2,rust,cobol,algol68
--enable-ld=yes --enable-gold=yes --enable-libstdcxx-time=yes
--enable-linker-build-id --enable-lto --enable-plugins --enable-threads=posix
--with-pkgversion=Compiler-Explorer-Build-gcc--binutils-2.44
Thread model: posix
Supported LTO compression algorithms: zlib
gcc version 16.2.0 (Compiler-Explorer-Build-gcc--binutils-2.44)
```