Subject: Re: bug#72695: CC Mode 5.35.2 (C++//l); c++-mode misindents 'if constexpr' inside lambdas

From: Alan Mackenzie <[email-masked]>
Date: Sat, 31 Aug 2024 17:01:07 +0000

Hello again, Arsen.

On Sat, Aug 31, 2024 at 02:54:01 +0200, Arsen Arsenović wrote:
> Hello, Alan,

> Alan Mackenzie <[email-masked]> writes:

> > Hello, Arsen.

> > A short progress report.

> > On Wed, Aug 21, 2024 at 22:40:02 +0200, Arsen Arsenović wrote:
> >> Hello Alan,

> >> Alan Mackenzie <[email-masked]> writes:
> >> > Thanks for the bug report!

> >> My pleasure.  Thanks for being as fast as always.

> > I apologise for not having been as fast over the last few days.  Partly,
> > I'm bringing up a new computer, which never goes as fast as one would
> > like.

> No worries, of course!  Hope the setup went well.

It's going well, thanks!  At the current rate of progress, I'll have my
mail setup set up by this time next week, and that is the point at
which, at least for me, my new machine becomes my current machine.

> > But mainly I'm struggling with the unfathomable complexity of C++ lambda
> > functions, as documented on
> > https://en.cppreference.com/w/cpp/language/lambda.

> It should be quite simple - just a few optional productions preceded by
> an introducer production (see https://eel.is/c++draft/expr.prim.lambda
> for a formal grammar - this is the text from the standard draft).

> > In my not so humble opinion, the C++ standards people have completely
> > lost the plot.

> Should help be necessary, I do work on G++ so I'm quite familiar with
> the language.  Feel free to ping me.

Thanks!  It's just that the explanatory text on en.cppreference.com
about just lambda functions takes 20 scrolls of ~45 lines per scroll,
coming out at many hundreds of lines, just for lambda functions.  I feel
that the entire language part of Emacs Lisp could fit into that quite
comfortably, at the same density.

> > The number of special cases in just the syntax, apparently just to
> > save a few characters from a program's text, has got beyond the point
> > where anybody can grasp it.  Nobody in this group of people appears to
> > be able to say "STOP!", though somebody sorely needs to.  I dread to
> > think what will be coming out in C++26.  :-(

> It really shouldn't be that bad - I'll help demystify if need be.

Thanks!

> > Anyhow, I've spent all afternoon on the problem so far, and I think I can
> > see my way to a solution.  It will still take a few more days yet, I'm
> > afraid.

> Thank you very much!  My apologies for the trouble.

No apologies called for.  You raised a bug report, for which thanks!

Anyhow, I've got a first version, incompletely tested, of a new handling
for a lambda expression.  The patch is not nearly as big as it looks,
two thirds of it is just re-indenting a large function I put an extra
`if' form aroun.

Would you please apply this patch, which should fix the test case, and
then test it out on your real C++ code, then let me know how it's gone.
I'll see if I can get some new test cases written.  Thanks!

diff -r 48ad661a2d7d cc-engine.el
--- a/cc-engine.el	Mon Jul 22 12:09:34 2024 +0000
+++ b/cc-engine.el	Sat Aug 31 16:44:39 2024 +0000
@@ -13178,229 +13178,223 @@
   ;;
   ;; Here, "brace list" does not include the body of an enum.
   (save-excursion
-    (let ((start (point))
-	  (braceassignp 'dontknow)
-	  inexpr-brace-list bufpos macro-start res pos after-type-id-pos
-	  pos2 in-paren parens-before-brace
-	  paren-state paren-pos)
-
-      (setq res
-	    (or (progn (c-backward-syntactic-ws)
-		       (c-back-over-compound-identifier))
-		(c-backward-token-2 1 t lim)))
-      ;; Checks to do only on the first sexp before the brace.
-      ;; Have we a C++ initialization, without an "="?
-      (if (and (c-major-mode-is 'c++-mode)
-	       (cond
-		((and (or (not (memq res '(t 0)))
-			  (eq (char-after) ?,))
-		      (setq paren-state (c-parse-state))
-		      (setq paren-pos (c-pull-open-brace paren-state))
-		      (eq (char-after paren-pos) ?\())
-		 (goto-char paren-pos)
-		 (setq braceassignp 'c++-noassign
-		       in-paren 'in-paren))
-		((looking-at c-pre-brace-non-bracelist-key)
-		 (setq braceassignp nil))
-		((and
-		  (looking-at c-fun-name-substitute-key)
-		  (not (eq (char-after (match-end 0)) ?_)))
-		 (setq braceassignp nil))
-		((looking-at c-return-key))
-		((and (looking-at c-symbol-start)
-		      (not (looking-at c-keywords-regexp)))
-		 (if (save-excursion
-		       (and (zerop (c-backward-token-2 1 t lim))
-			    (looking-at c-pre-id-bracelist-key)))
-		     (setq braceassignp 'c++-noassign)
-		   (setq after-type-id-pos (point))))
-		((eq (char-after) ?\()
-		 (setq parens-before-brace t)
-		 ;; Have we a requires with a parenthesis list?
-		 (when (save-excursion
-			 (and (zerop (c-backward-token-2 1 nil lim))
-			      (looking-at c-fun-name-substitute-key)
-			      (not (eq (char-after (match-end 0)) ?_))))
+    (unless (and (c-major-mode-is 'c++-mode)
+		 (c-backward-over-lambda-expression lim))
+      (let ((start (point))
+	    (braceassignp 'dontknow)
+	    inexpr-brace-list bufpos macro-start res pos after-type-id-pos
+	    pos2 in-paren ;; parens-before-brace STOUGH, 2024-08-31
+	    paren-state paren-pos)
+
+	(setq res
+	      (or (progn (c-backward-syntactic-ws)
+			 (c-back-over-compound-identifier))
+		  (c-backward-token-2 1 t lim)))
+	;; Checks to do only on the first sexp before the brace.
+	;; Have we a C++ initialization, without an "="?
+	(if (and (c-major-mode-is 'c++-mode)
+		 (cond
+		  ((and (or (not (memq res '(t 0)))
+			    (eq (char-after) ?,))
+			(setq paren-state (c-parse-state))
+			(setq paren-pos (c-pull-open-brace paren-state))
+			(eq (char-after paren-pos) ?\())
+		   (goto-char paren-pos)
+		   (setq braceassignp 'c++-noassign
+			 in-paren 'in-paren))
+		  ((looking-at c-pre-brace-non-bracelist-key)
 		   (setq braceassignp nil))
-		 nil)
-		(t nil))
-	       (save-excursion
-		 (cond
-		  ((or (not (memq res '(t 0)))
-		       (eq (char-after) ?,))
-		   (and (setq paren-state (c-parse-state))
-			(setq paren-pos (c-pull-open-brace paren-state))
-			(eq (char-after paren-pos) ?\()
-			(setq in-paren 'in-paren)
-			(goto-char paren-pos)))
-		  ((looking-at c-pre-brace-non-bracelist-key))
+		  ((and
+		    (looking-at c-fun-name-substitute-key)
+		    (not (eq (char-after (match-end 0)) ?_)))
+		   (setq braceassignp nil))
 		  ((looking-at c-return-key))
 		  ((and (looking-at c-symbol-start)
-			(not (looking-at c-keywords-regexp))
-			(save-excursion
-			  (and (zerop (c-backward-token-2 1 t lim))
-			       (looking-at c-pre-id-bracelist-key)))))
-		  (t (setq after-type-id-pos (point))
-		     nil))))
-	  (setq braceassignp 'c++-noassign))
-
-      (when (and c-opt-inexpr-brace-list-key
-		 (eq (char-after) ?\[))
-	;; In Java, an initialization brace list may follow
-	;; directly after "new Foo[]", so check for a "new"
-	;; earlier.
-	(while (eq braceassignp 'dontknow)
-	  (setq braceassignp
-		(cond ((/= (c-backward-token-2 1 t lim) 0) nil)
-		      ((looking-at c-opt-inexpr-brace-list-key)
-		       (setq inexpr-brace-list t)
-		       t)
-		      ((looking-at "\\sw\\|\\s_\\|[.[]")
-		       ;; Carry on looking if this is an
-		       ;; identifier (may contain "." in Java)
-		       ;; or another "[]" sexp.
-		       'dontknow)
-		      (t nil)))))
-
-      (setq pos (point))
-      (cond
-       ((not braceassignp)
-	nil)
-       ((and after-type-id-pos
-	     (goto-char after-type-id-pos)
-	     (setq res (c-back-over-member-initializers))
-	     (goto-char res)
-	     (eq (car (c-beginning-of-decl-1 lim)) 'same))
-	(cons (point) nil))		; Return value.
-
-       ((and after-type-id-pos
-	     (progn
-	       (c-backward-syntactic-ws)
-	       (eq (char-before) ?\()))
-	;; Single identifier between '(' and '{'.  We have a bracelist.
-	(cons after-type-id-pos 'in-paren))
-
-       ;; Are we at the parens of a C++ lambda expression?
-       ((and parens-before-brace
-	     (save-excursion
-	       (and
-		(zerop (c-backward-token-2 1 t lim))
-		(c-looking-at-c++-lambda-capture-list))))
-	nil)			     ; a lambda expression isn't a brace list.
-
-       (t
-	(goto-char pos)
-	(when (eq braceassignp 'dontknow)
-	  (let* ((cache-entry (and containing-sexp
-				   (c-laomib-get-cache containing-sexp pos)))
-		 (lim2 (or (cadr cache-entry) lim))
-		 sub-bassign-p)
-	    (if cache-entry
-		(cond
-		 ((<= (point) (cadr cache-entry))
-		  ;; We're inside the region we've already scanned over, so
-		  ;; just go to that scan's end position.
-		  (goto-char (nth 2 cache-entry))
-		  (setq braceassignp (nth 3 cache-entry)))
-		 ((> (point) (cadr cache-entry))
-		  ;; We're beyond the previous scan region, so just scan as
-		  ;; far as the end of that region.
-		  (setq sub-bassign-p (c-laomib-loop lim2))
-		  (if (<= (point) (cadr cache-entry))
-		      (progn
-			(c-laomib-put-cache containing-sexp
-					    start (nth 2 cache-entry)
-					    (nth 3 cache-entry) ;; sub-bassign-p
-					    )
-			(setq braceassignp (nth 3 cache-entry))
-			(goto-char (nth 2 cache-entry)))
-		    (c-laomib-put-cache containing-sexp
-					start (point) sub-bassign-p)
-		    (setq braceassignp sub-bassign-p)))
-		 (t))
-
-	      (setq braceassignp (c-laomib-loop lim))
-	      (when lim
-		(c-laomib-put-cache lim start (point) braceassignp)))))
-
+			(not (looking-at c-keywords-regexp)))
+		   (if (save-excursion
+			 (and (zerop (c-backward-token-2 1 t lim))
+			      (looking-at c-pre-id-bracelist-key)))
+		       (setq braceassignp 'c++-noassign)
+		     (setq after-type-id-pos (point))))
+		  ((eq (char-after) ?\()
+		   ;; (setq parens-before-brace t) STOUGH, 2024-08-31
+		   ;; Have we a requires with a parenthesis list?
+		   (when (save-excursion
+			   (and (zerop (c-backward-token-2 1 nil lim))
+				(looking-at c-fun-name-substitute-key)
+				(not (eq (char-after (match-end 0)) ?_))))
+		     (setq braceassignp nil))
+		   nil)
+		  (t nil))
+		 (save-excursion
+		   (cond
+		    ((or (not (memq res '(t 0)))
+			 (eq (char-after) ?,))
+		     (and (setq paren-state (c-parse-state))
+			  (setq paren-pos (c-pull-open-brace paren-state))
+			  (eq (char-after paren-pos) ?\()
+			  (setq in-paren 'in-paren)
+			  (goto-char paren-pos)))
+		    ((looking-at c-pre-brace-non-bracelist-key))
+		    ((looking-at c-return-key))
+		    ((and (looking-at c-symbol-start)
+			  (not (looking-at c-keywords-regexp))
+			  (save-excursion
+			    (and (zerop (c-backward-token-2 1 t lim))
+				 (looking-at c-pre-id-bracelist-key)))))
+		    (t (setq after-type-id-pos (point))
+		       nil))))
+	    (setq braceassignp 'c++-noassign))
+
+	(when (and c-opt-inexpr-brace-list-key
+		   (eq (char-after) ?\[))
+	  ;; In Java, an initialization brace list may follow
+	  ;; directly after "new Foo[]", so check for a "new"
+	  ;; earlier.
+	  (while (eq braceassignp 'dontknow)
+	    (setq braceassignp
+		  (cond ((/= (c-backward-token-2 1 t lim) 0) nil)
+			((looking-at c-opt-inexpr-brace-list-key)
+			 (setq inexpr-brace-list t)
+			 t)
+			((looking-at "\\sw\\|\\s_\\|[.[]")
+			 ;; Carry on looking if this is an
+			 ;; identifier (may contain "." in Java)
+			 ;; or another "[]" sexp.
+			 'dontknow)
+			(t nil)))))
+
+	(setq pos (point))
 	(cond
-	 (braceassignp
-	  ;; We've hit the beginning of the aggregate list.
-	  (setq pos2 (point))
-	  (cons
-	   (if (eq (c-beginning-of-statement-1 containing-sexp) 'same)
-	       (point)
-	     pos2)
-	   (or in-paren inexpr-brace-list)))
+	 ((not braceassignp)
+	  nil)
+	 ((and after-type-id-pos
+	       (goto-char after-type-id-pos)
+	       (setq res (c-back-over-member-initializers))
+	       (goto-char res)
+	       (eq (car (c-beginning-of-decl-1 lim)) 'same))
+	  (cons (point) nil))		; Return value.
+
 	 ((and after-type-id-pos
-	       (save-excursion
-		 (when (eq (char-after) ?\;)
-		   (c-forward-over-token-and-ws t))
-		 (setq bufpos (point))
-		 (when (looking-at c-opt-<>-sexp-key)
-		   (c-forward-over-token-and-ws)
-		   (when (and (eq (char-after) ?<)
-			      (c-get-char-property (point) 'syntax-table))
-		     (c-go-list-forward nil after-type-id-pos)
-		     (c-forward-syntactic-ws)))
-		 (if (and (not (eq (point) after-type-id-pos))
-			  (or (not (looking-at c-class-key))
-			      (save-excursion
-				(goto-char (match-end 1))
-				(c-forward-syntactic-ws)
-				(not (eq (point) after-type-id-pos)))))
-		     (progn
-		       (setq res
-			     (c-forward-decl-or-cast-1 (c-point 'bosws)
-						       nil nil))
-		       (and (consp res)
-			    (cond
-			     ((eq (car res) after-type-id-pos))
-			     ((> (car res) after-type-id-pos) nil)
-			     (t
-			      (catch 'find-decl
+	       (progn
+		 (c-backward-syntactic-ws)
+		 (eq (char-before) ?\()))
+	  ;; Single identifier between '(' and '{'.  We have a bracelist.
+	  (cons after-type-id-pos 'in-paren))
+
+	 (t
+	  (goto-char pos)
+	  (when (eq braceassignp 'dontknow)
+	    (let* ((cache-entry (and containing-sexp
+				     (c-laomib-get-cache containing-sexp pos)))
+		   (lim2 (or (cadr cache-entry) lim))
+		   sub-bassign-p)
+	      (if cache-entry
+		  (cond
+		   ((<= (point) (cadr cache-entry))
+		    ;; We're inside the region we've already scanned over, so
+		    ;; just go to that scan's end position.
+		    (goto-char (nth 2 cache-entry))
+		    (setq braceassignp (nth 3 cache-entry)))
+		   ((> (point) (cadr cache-entry))
+		    ;; We're beyond the previous scan region, so just scan as
+		    ;; far as the end of that region.
+		    (setq sub-bassign-p (c-laomib-loop lim2))
+		    (if (<= (point) (cadr cache-entry))
+			(progn
+			  (c-laomib-put-cache containing-sexp
+					      start (nth 2 cache-entry)
+					      (nth 3 cache-entry) ;; sub-bassign-p
+					      )
+			  (setq braceassignp (nth 3 cache-entry))
+			  (goto-char (nth 2 cache-entry)))
+		      (c-laomib-put-cache containing-sexp
+					  start (point) sub-bassign-p)
+		      (setq braceassignp sub-bassign-p)))
+		   (t))
+
+		(setq braceassignp (c-laomib-loop lim))
+		(when lim
+		  (c-laomib-put-cache lim start (point) braceassignp)))))
+
+	  (cond
+	   (braceassignp
+	    ;; We've hit the beginning of the aggregate list.
+	    (setq pos2 (point))
+	    (cons
+	     (if (eq (c-beginning-of-statement-1 containing-sexp) 'same)
+		 (point)
+	       pos2)
+	     (or in-paren inexpr-brace-list)))
+	   ((and after-type-id-pos
+		 (save-excursion
+		   (when (eq (char-after) ?\;)
+		     (c-forward-over-token-and-ws t))
+		   (setq bufpos (point))
+		   (when (looking-at c-opt-<>-sexp-key)
+		     (c-forward-over-token-and-ws)
+		     (when (and (eq (char-after) ?<)
+				(c-get-char-property (point) 'syntax-table))
+		       (c-go-list-forward nil after-type-id-pos)
+		       (c-forward-syntactic-ws)))
+		   (if (and (not (eq (point) after-type-id-pos))
+			    (or (not (looking-at c-class-key))
 				(save-excursion
-				  (goto-char (car res))
-				  (c-do-declarators
-				   (point-max) t nil nil
-				   (lambda (id-start _id-end _tok _not-top _func _init)
-				     (cond
-				      ((> id-start after-type-id-pos)
-				       (throw 'find-decl nil))
-				      ((eq id-start after-type-id-pos)
-				       (throw 'find-decl t)))))
-				  nil))))))
-		   (save-excursion
-		     (goto-char start)
-		     (not (c-looking-at-statement-block))))))
-	  (cons bufpos (or in-paren inexpr-brace-list)))
-	 ((or (eq (char-after) ?\;)
-	      ;; Brace lists can't contain a semicolon, so we're done.
-	      (save-excursion
-		(c-backward-syntactic-ws)
-		(eq (char-before) ?}))
-	      ;; They also can't contain a bare }, which is probably the end
-	      ;; of a function.
-	      )
-	  nil)
-	 ((and (setq macro-start (point))
-	       (c-forward-to-cpp-define-body)
-	       (eq (point) start))
-	  ;; We've a macro whose expansion starts with the '{'.
-	  ;; Heuristically, if we have a ';' in it we've not got a
-	  ;; brace list, otherwise we have.
-	  (let ((macro-end (progn (c-end-of-macro) (point))))
-	    (goto-char start)
-	    (forward-char)
-	    (if (and (c-syntactic-re-search-forward "[;,]" macro-end t t)
-		     (eq (char-before) ?\;))
-		nil
-	      (cons macro-start nil)))) ; (2016-08-30): Lazy! We have no
+				  (goto-char (match-end 1))
+				  (c-forward-syntactic-ws)
+				  (not (eq (point) after-type-id-pos)))))
+		       (progn
+			 (setq res
+			       (c-forward-decl-or-cast-1 (c-point 'bosws)
+							 nil nil))
+			 (and (consp res)
+			      (cond
+			       ((eq (car res) after-type-id-pos))
+			       ((> (car res) after-type-id-pos) nil)
+			       (t
+				(catch 'find-decl
+				  (save-excursion
+				    (goto-char (car res))
+				    (c-do-declarators
+				     (point-max) t nil nil
+				     (lambda (id-start _id-end _tok _not-top _func _init)
+				       (cond
+					((> id-start after-type-id-pos)
+					 (throw 'find-decl nil))
+					((eq id-start after-type-id-pos)
+					 (throw 'find-decl t)))))
+				    nil))))))
+		     (save-excursion
+		       (goto-char start)
+		       (not (c-looking-at-statement-block))))))
+	    (cons bufpos (or in-paren inexpr-brace-list)))
+	   ((or (eq (char-after) ?\;)
+		;; Brace lists can't contain a semicolon, so we're done.
+		(save-excursion
+		  (c-backward-syntactic-ws)
+		  (eq (char-before) ?}))
+		;; They also can't contain a bare }, which is probably the end
+		;; of a function.
+		)
+	    nil)
+	   ((and (setq macro-start (point))
+		 (c-forward-to-cpp-define-body)
+		 (eq (point) start))
+	    ;; We've a macro whose expansion starts with the '{'.
+	    ;; Heuristically, if we have a ';' in it we've not got a
+	    ;; brace list, otherwise we have.
+	    (let ((macro-end (progn (c-end-of-macro) (point))))
+	      (goto-char start)
+	      (forward-char)
+	      (if (and (c-syntactic-re-search-forward "[;,]" macro-end t t)
+		       (eq (char-before) ?\;))
+		  nil
+		(cons macro-start nil)))) ; (2016-08-30): Lazy! We have no
 					; languages where
 					; `c-opt-inexpr-brace-list-key' is
 					; non-nil and we have macros.
-	 (t t))))			;; The caller can go up one level.
-      )))
+	   (t t))))			;; The caller can go up one level.
+	))))
 
 (defun c-inside-bracelist-p (containing-sexp paren-state accept-in-paren)
   ;; return the buffer position of the beginning of the brace list statement
@@ -13863,6 +13857,71 @@
 	 (looking-at c-pre-lambda-tokens-re)))
    (not (c-in-literal))))
 
+(defun c-looking-at-c++-lambda-expression (&optional lim)
+  ;; If point is at the [ opening a C++ lambda expressions's capture list,
+  ;; and the lambda expression is complete, return the position of the { which
+  ;; opens the body form, otherwise return nil.  LIM is the limit for forward
+  ;; searching for the {.
+  (let ((here (point))
+	got-params)
+    (when (and (c-looking-at-c++-lambda-capture-list)
+	       (c-go-list-forward nil lim))
+      (c-forward-syntactic-ws lim)
+      (when (c-forward-<>-arglist t)
+	(c-forward-syntactic-ws lim)
+	(when (looking-at "requires\\([^a-zA-Z0-9_]\\|$\\)")
+	  (c-forward-c++-requires-clause lim nil)))
+      (when (and (eq (char-after) ?\()
+		 (c-go-list-forward nil lim))
+	(setq got-params t)
+	(c-forward-syntactic-ws lim))
+      (while (and c-lambda-spec-key (looking-at c-lambda-spec-key))
+	(goto-char (match-end 1))
+	(c-forward-syntactic-ws lim))
+      (let (after-except-pos)
+	(cond
+	 ((save-excursion
+	    (and (looking-at "throw\\([^a-zA-Z0-9_]\\|$\\)")
+		 (progn (goto-char (match-beginning 1))
+			(c-forward-syntactic-ws lim)
+			(eq (char-after) ?\())
+		 (c-go-list-forward nil lim)
+		 (progn (c-forward-syntactic-ws lim)
+			(setq after-except-pos (point)))))
+	  (goto-char after-except-pos)
+	  (c-forward-syntactic-ws lim))
+	 ((looking-at "noexcept\\([^a-zA-Z0-9_]\\|$\\)")
+	  (c-forward-syntactic-ws lim)
+	  (setq after-except-pos (point))
+	  (when (and (eq (char-after) ?\()
+		     (c-go-list-forward nil lim))
+	    (c-forward-syntactic-ws lim)))))
+      (and (looking-at c-haskell-op-re)
+	   (goto-char (match-end 0))
+	   (c-forward-syntactic-ws lim)
+	   (c-forward-type t))		; t is BRACE-BLOCK-TOO.
+      (and got-params
+	   (looking-at "requires\\([^a-zA-Z0-9_]\\|$\\)")
+	   (c-forward-c++-requires-clause lim nil))
+      (prog1 (and (eq (char-after) ?{)
+		  (point))
+	(goto-char here)))))
+
+(defun c-backward-over-lambda-expression (&optional lim)
+  ;; Point is at a {.  Move back over the lambda expression this is a part of,
+  ;; stopping at the [ of the capture list, if this is the case, returning
+  ;; the position of that opening bracket.  If we're not at such a list, leave
+  ;; point unchanged and return nil.
+  (let ((here (point)))
+    (c-syntactic-skip-backward "^;}]" lim t)
+    (if (and (eq (char-before) ?\])
+	     (c-go-list-backward nil lim)
+	     (eq (c-looking-at-c++-lambda-expression (1+ here))
+		 here))
+	(point)
+      (goto-char here)
+      nil)))
+
 (defun c-c++-vsemi-p (&optional pos)
   ;; C++ Only - Is there a "virtual semicolon" at POS or point?
   ;; (See cc-defs.el for full details of "virtual semicolons".)
diff -r 48ad661a2d7d cc-langs.el
--- a/cc-langs.el	Mon Jul 22 12:09:34 2024 +0000
+++ b/cc-langs.el	Sat Aug 31 16:44:39 2024 +0000
@@ -2639,6 +2639,19 @@
   t (c-make-keywords-re t (c-lang-const c-equals-type-clause-kwds)))
 (c-lang-defvar c-equals-type-clause-key (c-lang-const c-equals-type-clause-key))
 
+(c-lang-defconst c-lambda-spec-kwds
+  "Keywords which are specifiers of certain elements of a C++ lambda function.
+This is only used in C++ Mode."
+  t nil
+  c++ '("mutable" "constexpr" "consteval" "static"))
+
+(c-lang-defconst c-lambda-spec-key
+  ;; A regular expression which matches a member of `c-lambda-spec-kwds',
+  ;; or nil.
+  t (if (c-lang-const c-lambda-spec-kwds)
+	(c-make-keywords-re t (c-lang-const c-lambda-spec-kwds))))
+(c-lang-defvar c-lambda-spec-key (c-lang-const c-lambda-spec-key))
+
 (c-lang-defconst c-equals-nontype-decl-kwds
   "Keywords which are followed by an identifier then an \"=\"
 sign, which declares the identifier to be something other than a

> Have a lovely day.
> -- 
> Arsen Arsenović

-- 
Alan Mackenzie (Nuremberg, Germany).