[clang] [Clang][C++2d] Implement defaulted postfix increment and decrement operators (P3668R4) (PR #229560)

Muhammad Bassiouni via cfe-commits cfe-commits at lists.llvm.org
Sat Oct 17 22:12:28 PDT 2026


https://github.com/bassiounix updated https://github.com/llvm/llvm-project/pull/229560

>From 4559ad78ffc2ea4b580de598468239211978c59c Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Tue, 6 Oct 2026 23:50:26 +0300
Subject: [PATCH 01/35] [Clang][C++2d] Implement P3668R4: defaulted postfix
 increment and decrement operators

Allow the postfix `operator++(int)` and `operator--(int)` to be explicitly
defaulted, as specified by P3668R4 ([over.inc.default]). The implicit
definition of a defaulted postfix operator for a type C is equivalent to

  C tmp(c);
  ++c;       // or --c
  return tmp;

where c denotes `*this` for an implicit object member function and the first
parameter otherwise. The copy is direct-initialized and `tmp` is a candidate
for named return value optimization.

The declaration requirements of [over.inc.default]p1 are checked: the first
parameter (counting the implicit object parameter) must be a reference to
C or to volatile C, the declared return type must be C, C must be complete,
and a definition as defaulted that appears in a class must be the first
declaration. Function templates and the prefix forms cannot be defaulted.

The function is defined as deleted per [over.inc.default]p2 when C has no
usable copy constructor, a deleted or inaccessible destructor, or no usable
prefix operator; the reason is explained with notes, and using such an
operator produces a dedicated error. The exception specification is computed
from the implicit definition ([except.spec]p10). A defaulted postfix operator
is implicitly inline and, following the rules for other defaulted functions,
implicitly constexpr when it is constexpr-suitable (C++23 and later) or
constexpr-compatible (C++14 through C++20).

Name lookup of the prefix operator is performed from a context equivalent to
the function body, also for `= default` at namespace scope: the parser now
passes the current scope to SetFunctionBodyKind/SetDeclDefaulted so that the
unqualified lookup results can be stored with the function, in the same way
as for defaulted comparison operators.

Like other recent C++ features, this is available in all C++ language modes
as an extension, diagnosed with -Wc++2d-extensions and -Wpre-c++2d-compat.

Co-Authored-By: Claude Fable 5.1 <noreply at anthropic.com>
---
 clang/docs/LanguageExtensions.md              |   1 +
 clang/docs/ReleaseNotes.md                    |   7 +
 clang/include/clang/AST/Decl.h                |  39 +-
 .../clang/Basic/DiagnosticCommonKinds.td      |   3 +-
 .../clang/Basic/DiagnosticSemaKinds.td        |  67 ++
 clang/include/clang/Sema/Sema.h               |  26 +-
 clang/lib/AST/Decl.cpp                        |  18 +-
 clang/lib/Parse/ParseDecl.cpp                 |   2 +-
 clang/lib/Parse/ParseDeclCXX.cpp              |   2 +-
 clang/lib/Parse/Parser.cpp                    |   3 +-
 clang/lib/Sema/SemaDeclCXX.cpp                | 685 +++++++++++++++++-
 clang/lib/Sema/SemaExpr.cpp                   |   9 +-
 clang/lib/Sema/SemaOverload.cpp               |  13 +
 clang/lib/Sema/SemaTemplateInstantiate.cpp    |   7 +
 .../ast-dump-defaulted-postfix-operators.cpp  |  92 +++
 .../over.inc/over.inc.default/p1.cpp          | 205 ++++++
 .../over.inc/over.inc.default/p2.cpp          | 192 +++++
 .../over.inc/over.inc.default/p3.cpp          | 263 +++++++
 .../cxx2d-defaulted-postfix-operators.cpp     |  53 ++
 .../PCH/cxx2d-defaulted-postfix-operators.cpp |  59 ++
 .../cxx2d-defaulted-postfix-operators.cpp     | 108 +++
 clang/www/cxx_status.html                     |   2 +-
 22 files changed, 1830 insertions(+), 26 deletions(-)
 create mode 100644 clang/test/AST/ast-dump-defaulted-postfix-operators.cpp
 create mode 100644 clang/test/CXX/over/over.oper/over.inc/over.inc.default/p1.cpp
 create mode 100644 clang/test/CXX/over/over.oper/over.inc/over.inc.default/p2.cpp
 create mode 100644 clang/test/CXX/over/over.oper/over.inc/over.inc.default/p3.cpp
 create mode 100644 clang/test/CodeGenCXX/cxx2d-defaulted-postfix-operators.cpp
 create mode 100644 clang/test/PCH/cxx2d-defaulted-postfix-operators.cpp
 create mode 100644 clang/test/SemaCXX/cxx2d-defaulted-postfix-operators.cpp

diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md
index 7ec585d8362927..4e8047e596cbc0 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -1784,6 +1784,7 @@ More information can be found [here](Modules.md).
 | Trivial Relocatability                        | \_\_cpp_trivial_relocatability     | C++26         | C++03         |
 |``auto()`` cast                                | \_\_cpp_auto_cast                  | C++26         | C++03         |
 | Pack Indexing for Template Names              | \_\_cpp_pack_indexing >= 202606L   | C++2d         | C++03         |
+| Defaulted postfix `++` and `--` operators     |                                    | C++2d         | C++03         |
 | Designated initializers (N494)                |                                    | C99           | C89           |
 | `_Complex` (N693)                             |                                    | C99           | C89, C++      |
 | `_Bool` (N815)                                |                                    | C99           | C89           |
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index bf190df9769ddf..bcc9662c59ecf5 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -165,6 +165,13 @@ features cannot lower the translation-unit ABI level;
   `-Wc++2d-extensions` and `-Wpre-c++2d-compat`, and `__cpp_pack_indexing` is
   bumped to `202606L`.
 
+- Clang now supports [P3668R4](https://wg21.link/p3668r4) (Defaulting postfix
+  increment and decrement operations), which allows the postfix `operator++(int)`
+  and `operator--(int)` to be explicitly defaulted. The implicit definition
+  copies the object, applies the corresponding prefix operator, and returns the
+  copy. This is available in all C++ language modes as an extension, controlled
+  by `-Wc++2d-extensions` and `-Wpre-c++2d-compat`.
+
 #### C++2c Feature Support
 
 - Added `__builtin_type_order` for compatibility with GCC as part of the
diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index c64dc8c02d40e0..34e7f4ea701cdd 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -2026,6 +2026,17 @@ enum class CXXSpecialMemberKind {
   Invalid
 };
 
+/// Kinds of defaulted postfix increment and decrement operator functions
+/// (C++2d [over.inc.default]).
+enum class PostfixOperatorKind : unsigned char {
+  /// This is not a defaultable postfix increment or decrement operator.
+  None,
+  /// This is a postfix increment operator, 'operator++(int)'.
+  Increment,
+  /// This is a postfix decrement operator, 'operator--(int)'.
+  Decrement,
+};
+
 /// Kinds of defaulted comparison operator functions.
 enum class DefaultedComparisonKind : unsigned char {
   /// This is not a defaultable comparison operator.
@@ -2124,17 +2135,26 @@ class FunctionDecl : public DeclaratorDecl,
     LLVM_PREFERRED_TYPE(CXXSpecialMemberKind)
     unsigned SpecialMember : 8;
     unsigned Comparison : 8;
+    LLVM_PREFERRED_TYPE(PostfixOperatorKind)
+    unsigned PostfixOperator : 2;
 
   public:
     DefaultedFunctionKind()
         : SpecialMember(llvm::to_underlying(CXXSpecialMemberKind::Invalid)),
-          Comparison(llvm::to_underlying(DefaultedComparisonKind::None)) {}
+          Comparison(llvm::to_underlying(DefaultedComparisonKind::None)),
+          PostfixOperator(llvm::to_underlying(PostfixOperatorKind::None)) {}
     DefaultedFunctionKind(CXXSpecialMemberKind CSM)
         : SpecialMember(llvm::to_underlying(CSM)),
-          Comparison(llvm::to_underlying(DefaultedComparisonKind::None)) {}
+          Comparison(llvm::to_underlying(DefaultedComparisonKind::None)),
+          PostfixOperator(llvm::to_underlying(PostfixOperatorKind::None)) {}
     DefaultedFunctionKind(DefaultedComparisonKind Comp)
         : SpecialMember(llvm::to_underlying(CXXSpecialMemberKind::Invalid)),
-          Comparison(llvm::to_underlying(Comp)) {}
+          Comparison(llvm::to_underlying(Comp)),
+          PostfixOperator(llvm::to_underlying(PostfixOperatorKind::None)) {}
+    DefaultedFunctionKind(PostfixOperatorKind PO)
+        : SpecialMember(llvm::to_underlying(CXXSpecialMemberKind::Invalid)),
+          Comparison(llvm::to_underlying(DefaultedComparisonKind::None)),
+          PostfixOperator(llvm::to_underlying(PO)) {}
 
     bool isSpecialMember() const {
       return static_cast<CXXSpecialMemberKind>(SpecialMember) !=
@@ -2144,9 +2164,13 @@ class FunctionDecl : public DeclaratorDecl,
       return static_cast<DefaultedComparisonKind>(Comparison) !=
              DefaultedComparisonKind::None;
     }
+    bool isPostfixOperator() const {
+      return static_cast<PostfixOperatorKind>(PostfixOperator) !=
+             PostfixOperatorKind::None;
+    }
 
     explicit operator bool() const {
-      return isSpecialMember() || isComparison();
+      return isSpecialMember() || isComparison() || isPostfixOperator();
     }
 
     CXXSpecialMemberKind asSpecialMember() const {
@@ -2155,6 +2179,9 @@ class FunctionDecl : public DeclaratorDecl,
     DefaultedComparisonKind asComparison() const {
       return static_cast<DefaultedComparisonKind>(Comparison);
     }
+    PostfixOperatorKind asPostfixOperator() const {
+      return static_cast<PostfixOperatorKind>(PostfixOperator);
+    }
 
     /// Get the index of this function kind for use in diagnostics.
     unsigned getDiagnosticIndex() const {
@@ -2163,7 +2190,9 @@ class FunctionDecl : public DeclaratorDecl,
                     "invalid should have highest index");
       static_assert((unsigned)DefaultedComparisonKind::None == 0,
                     "none should be equal to zero");
-      return SpecialMember + Comparison;
+      static_assert((unsigned)PostfixOperatorKind::None == 0,
+                    "none should be equal to zero");
+      return SpecialMember + Comparison + PostfixOperator;
     }
   };
 
diff --git a/clang/include/clang/Basic/DiagnosticCommonKinds.td b/clang/include/clang/Basic/DiagnosticCommonKinds.td
index d8dfb3e306ed51..2a503cd76e0eba 100644
--- a/clang/include/clang/Basic/DiagnosticCommonKinds.td
+++ b/clang/include/clang/Basic/DiagnosticCommonKinds.td
@@ -97,7 +97,8 @@ def err_invalid_storage_class_in_func_decl : Error<
   "invalid storage class specifier in function declarator">;
 def err_expected_namespace_name : Error<"expected namespace name">;
 def err_default_special_members : Error<
-  "only special member functions %select{|and comparison operators }0"
+  "only special member functions%select{| and comparison operators|, "
+  "comparison operators, and postfix increment and decrement operators}0 "
   "may be defaulted">;
 def err_deleted_non_function : Error<
   "only functions can have deleted definitions">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index d293a9798da6a3..b2f72cab83243d 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -88,6 +88,8 @@ defm equals_this_lambda_capture : CXX20Compat<
   "explicit capture of 'this' with a capture default of '=' is">;
 defm capture_binding : CXX20Compat<"captured structured bindings are">;
 defm defaulted_comparison : CXX20Compat<"defaulted comparison operators are">;
+defm defaulted_postfix_operator : CXX29Compat<
+  "defaulted postfix increment and decrement operators are">;
 
 // C++23 compatibility with C++20 and earlier.
 defm constexpr_static_var : CXX23Compat<
@@ -10675,6 +10677,71 @@ def err_volatile_comparison_operator : Error<
 def err_ref_qualifier_comparison_operator : Error<
   "ref-qualifier '&&' is not allowed on a defaulted comparison operator">;
 
+// C++2d defaulted postfix increment and decrement operators
+// This corresponds to values of PostfixOperatorKind.
+def select_defaulted_postfix_operator_kind : TextSubstitution<
+  "%select{<ERROR>|postfix increment|postfix decrement}0 operator">;
+def err_defaulted_prefix_operator : Error<
+  "only the postfix form of 'operator%select{++|--}0' can be defaulted">;
+def err_defaulted_postfix_operator_template : Error<
+  "%sub{select_defaulted_postfix_operator_kind}0 template cannot be "
+  "defaulted">;
+def err_defaulted_postfix_operator_const : Error<
+  "defaulted member %sub{select_defaulted_postfix_operator_kind}0 cannot be "
+  "const-qualified">;
+def err_defaulted_postfix_operator_param : Error<
+  "invalid %select{explicit object|first}1 parameter type for defaulted "
+  "%sub{select_defaulted_postfix_operator_kind}0; found %2, expected %3 "
+  "or %4">;
+def err_defaulted_postfix_operator_param_unknown : Error<
+  "invalid first parameter type for defaulted non-member "
+  "%sub{select_defaulted_postfix_operator_kind}0; found %1, expected "
+  "reference to a non-const class or enumeration type">;
+def err_defaulted_postfix_operator_return_type : Error<
+  "return type for defaulted %sub{select_defaulted_postfix_operator_kind}0 "
+  "must be %1, not %2">;
+def err_defaulted_postfix_operator_incomplete : Error<
+  "cannot default %sub{select_defaulted_postfix_operator_kind}0 for "
+  "incomplete type %1">;
+def err_non_first_default_postfix_operator_deletes : Error<
+  "defaulting this %sub{select_defaulted_postfix_operator_kind}0 "
+  "would delete it after its first declaration">;
+def err_non_first_default_postfix_operator_in_class : Error<
+  "defaulting this %sub{select_defaulted_postfix_operator_kind}0 "
+  "is not allowed because it was already declared outside the class">;
+def warn_defaulted_postfix_operator_deleted : Warning<
+  "explicitly defaulted %sub{select_defaulted_postfix_operator_kind}0 is "
+  "implicitly deleted">, InGroup<DefaultedFunctionDeleted>;
+def note_defaulted_postfix_operator_no_viable : Note<
+  "defaulted %0 is implicitly deleted because there is no viable "
+  "%select{constructor to copy|prefix 'operator++' for|prefix 'operator--' "
+  "for}1 an lvalue of type %2">;
+def note_defaulted_postfix_operator_ambiguous : Note<
+  "defaulted %0 is implicitly deleted because %select{copy construction of|"
+  "prefix 'operator++' for|prefix 'operator--' for}1 an lvalue of type %2 "
+  "is ambiguous">;
+def note_defaulted_postfix_operator_calls_deleted : Note<
+  "defaulted %0 is implicitly deleted because it would invoke a deleted "
+  "%select{constructor|destructor|prefix 'operator++'|prefix 'operator--'}1">;
+def note_defaulted_postfix_operator_inaccessible : Note<
+  "defaulted %0 is implicitly deleted because it would invoke a "
+  "%select{private|protected}2 %select{constructor|destructor|"
+  "prefix 'operator++'|prefix 'operator--'}1 of %5">;
+def err_defaulted_postfix_operator_constexpr_mismatch : Error<
+  "defaulted definition of %sub{select_defaulted_postfix_operator_kind}0 "
+  "cannot be declared %select{constexpr|consteval}1 because it invokes a "
+  "non-constexpr function">;
+def note_defaulted_postfix_operator_not_constexpr_here : Note<
+  "non-constexpr %select{constructor|destructor|prefix 'operator++'|"
+  "prefix 'operator--'|conversion function}0 declared here">;
+def err_ovl_deleted_postfix_operator : Error<
+  "object of type %0 cannot be %select{<ERROR>|incremented|decremented}1 "
+  "because its defaulted %sub{select_defaulted_postfix_operator_kind}1 is "
+  "implicitly deleted">;
+def note_postfix_operator_synthesized_at : Note<
+  "in defaulted %sub{select_defaulted_postfix_operator_kind}0 for %1 "
+  "first required here">;
+
 def ext_implicit_exception_spec_mismatch : ExtWarn<
   "function previously declared with an %select{explicit|implicit}0 exception "
   "specification redeclared with an %select{implicit|explicit}0 exception "
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 3edf6f6c295002..6c615b482c7fc6 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -6214,6 +6214,21 @@ class Sema final : public SemaBase {
   void DefineDefaultedComparison(SourceLocation Loc, FunctionDecl *FD,
                                  DefaultedComparisonKind DCK);
 
+  /// Check a defaulted postfix increment or decrement operator function
+  /// (C++2d [over.inc.default]), defining it as deleted if necessary.
+  ///
+  /// \param S The scope in which the function is defaulted, used to perform
+  /// the unqualified name lookups needed by the implicit definition, or
+  /// \c nullptr if those lookups have already been performed.
+  ///
+  /// \returns \c true if the function is ill-formed.
+  bool CheckExplicitlyDefaultedPostfixOperator(Scope *S, FunctionDecl *FD,
+                                               PostfixOperatorKind Kind);
+  /// Synthesize the body of a defaulted postfix increment or decrement
+  /// operator function (C++2d [over.inc.default]p3).
+  void DefineDefaultedPostfixOperator(SourceLocation Loc, FunctionDecl *FD,
+                                      PostfixOperatorKind Kind);
+
   void CheckExplicitObjectMemberFunction(Declarator &D, DeclarationName Name,
                                          QualType R, bool IsLambda,
                                          DeclContext *DC = nullptr);
@@ -6531,10 +6546,17 @@ class Sema final : public SemaBase {
   void ActOnPureSpecifier(Decl *D, SourceLocation PureSpecLoc);
   void SetDeclDeleted(Decl *dcl, SourceLocation DelLoc,
                       StringLiteral *Message = nullptr);
-  void SetDeclDefaulted(Decl *dcl, SourceLocation DefaultLoc);
+  /// Mark the given declaration as explicitly defaulted.
+  ///
+  /// \param S The scope in which the function is defaulted, if it is
+  /// available. It is used to perform the unqualified name lookups needed by
+  /// the implicit definition of an out-of-class defaulted function.
+  void SetDeclDefaulted(Decl *dcl, SourceLocation DefaultLoc,
+                        Scope *S = nullptr);
 
   void SetFunctionBodyKind(Decl *D, SourceLocation Loc, FnBodyKind BodyKind,
-                           StringLiteral *DeletedMessage = nullptr);
+                           StringLiteral *DeletedMessage = nullptr,
+                           Scope *S = nullptr);
   void ActOnStartTrailingRequiresClause(Scope *S, Declarator &D);
   ExprResult ActOnFinishTrailingRequiresClause(ExprResult ConstraintExpr);
   ExprResult ActOnRequiresClause(ExprResult ConstraintExpr);
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index b145560508e956..aa0f37aa1a782e 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -3306,7 +3306,23 @@ FunctionDecl::getDefaultedFunctionKind() const {
       return CXXSpecialMemberKind::Destructor;
   }
 
-  switch (getDeclName().getCXXOverloadedOperator()) {
+  switch (OverloadedOperatorKind OO =
+              getDeclName().getCXXOverloadedOperator()) {
+  case OO_PlusPlus:
+  case OO_MinusMinus: {
+    // Only the postfix forms can be defaulted. They are distinguished from the
+    // prefix forms by their trailing 'int' parameter, so they have exactly two
+    // parameters once the implicit object parameter (if any) is counted.
+    unsigned NumParams = getNumParams();
+    if (const auto *MD = dyn_cast<CXXMethodDecl>(this);
+        MD && MD->isImplicitObjectMemberFunction())
+      ++NumParams;
+    if (NumParams != 2)
+      break;
+    return OO == OO_PlusPlus ? PostfixOperatorKind::Increment
+                             : PostfixOperatorKind::Decrement;
+  }
+
   case OO_EqualEqual:
     return DefaultedComparisonKind::Equal;
 
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index fbecae7b80fb97..c3da1f50ba23fe 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -2628,7 +2628,7 @@ Decl *Parser::ParseDeclarationAfterDeclaratorAndAttributes(
           << 0 /* default */;
       else
         Diag(ConsumeToken(), diag::err_default_special_members)
-            << getLangOpts().CPlusPlus20;
+            << (getLangOpts().CPlusPlus29 ? 2 : getLangOpts().CPlusPlus20);
     } else {
       InitializerScopeRAII InitScope(*this, D, ThisDecl);
 
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 1eeb0d21b68d7b..644b2e1f180dc3 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -3377,7 +3377,7 @@ ExprResult Parser::ParseCXXMemberInitializer(Decl *D, bool IsFunction,
             << 0 /* default */;
       else
         Diag(ConsumeToken(), diag::err_default_special_members)
-            << getLangOpts().CPlusPlus20;
+            << (getLangOpts().CPlusPlus29 ? 2 : getLangOpts().CPlusPlus20);
       return ExprError();
     }
   }
diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp
index c86ed6b2ea3f98..94681cb758c588 100644
--- a/clang/lib/Parse/Parser.cpp
+++ b/clang/lib/Parse/Parser.cpp
@@ -1378,7 +1378,8 @@ Decl *Parser::ParseFunctionDefinition(ParsingDeclarator &D,
   D.getMutableDeclSpec().abort();
 
   if (BodyKind != Sema::FnBodyKind::Other) {
-    Actions.SetFunctionBodyKind(Res, KWLoc, BodyKind, DeletedMessage);
+    Actions.SetFunctionBodyKind(Res, KWLoc, BodyKind, DeletedMessage,
+                                getCurScope());
     Stmt *GeneratedBody = Res ? Res->getBody() : nullptr;
     Actions.ActOnFinishFunctionBody(Res, GeneratedBody, false);
     return Res;
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 5345966419d40b..97a3114ca99a3a 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -6944,6 +6944,9 @@ static void DefineDefaultedFunction(Sema &S, FunctionDecl *FD,
   FunctionDecl::DefaultedFunctionKind DFK = FD->getDefaultedFunctionKind();
   if (DFK.isComparison())
     return S.DefineDefaultedComparison(DefaultLoc, FD, DFK.asComparison());
+  if (DFK.isPostfixOperator())
+    return S.DefineDefaultedPostfixOperator(DefaultLoc, FD,
+                                            DFK.asPostfixOperator());
 
   switch (DFK.asSpecialMember()) {
   case CXXSpecialMemberKind::DefaultConstructor:
@@ -7820,6 +7823,11 @@ ComputeDefaultedComparisonExceptionSpec(Sema &S, SourceLocation Loc,
                                         FunctionDecl *FD,
                                         DefaultedComparisonKind DCK);
 
+static Sema::ImplicitExceptionSpecification
+ComputeDefaultedPostfixOperatorExceptionSpec(Sema &S, SourceLocation Loc,
+                                             FunctionDecl *FD,
+                                             PostfixOperatorKind Kind);
+
 static Sema::ImplicitExceptionSpecification
 computeImplicitExceptionSpec(Sema &S, SourceLocation Loc, FunctionDecl *FD) {
   auto DFK = FD->getDefaultedFunctionKind();
@@ -7829,6 +7837,9 @@ computeImplicitExceptionSpec(Sema &S, SourceLocation Loc, FunctionDecl *FD) {
   if (DFK.isComparison())
     return ComputeDefaultedComparisonExceptionSpec(S, Loc, FD,
                                                    DFK.asComparison());
+  if (DFK.isPostfixOperator())
+    return ComputeDefaultedPostfixOperatorExceptionSpec(
+        S, Loc, FD, DFK.asPostfixOperator());
 
   auto *CD = cast<CXXConstructorDecl>(FD);
   assert(CD->getInheritedConstructor() &&
@@ -7887,6 +7898,13 @@ void Sema::CheckExplicitlyDefaultedFunction(Scope *S, FunctionDecl *FD) {
     }
   }
 
+  if (DefKind.isPostfixOperator()) {
+    if (CheckExplicitlyDefaultedPostfixOperator(S, FD,
+                                                DefKind.asPostfixOperator()))
+      FD->setInvalidDecl();
+    return;
+  }
+
   if (DefKind.isSpecialMember()
           ? CheckExplicitlyDefaultedSpecialMember(cast<CXXMethodDecl>(FD),
                                                   DefKind.asSpecialMember(),
@@ -9481,6 +9499,620 @@ ComputeDefaultedComparisonExceptionSpec(Sema &S, SourceLocation Loc,
   return ExceptSpec;
 }
 
+//===----------------------------------------------------------------------===//
+// C++2d [over.inc.default]: defaulted postfix increment and decrement operator
+// functions (P3668R4).
+//===----------------------------------------------------------------------===//
+
+namespace {
+/// The operations performed by the implicit definition of a defaulted postfix
+/// increment or decrement operator function. The values correspond to %select
+/// indices in the diagnostics explaining why such a function is deleted or is
+/// not constexpr.
+enum class PostfixOperatorStep {
+  Constructor,
+  Destructor,
+  PrefixIncrement,
+  PrefixDecrement,
+  ConversionFunction
+};
+
+/// Which diagnostics to produce while analyzing a defaulted postfix increment
+/// or decrement operator function.
+enum class PostfixOperatorDiagnoseKind {
+  NoDiagnostics,
+  ExplainDeleted,
+  ExplainConstexpr
+};
+
+/// Information about a defaulted postfix increment or decrement operator
+/// function, as determined by analyzeDefaultedPostfixOperator.
+struct DefaultedPostfixOperatorInfo {
+  /// Whether the function should be defined as deleted.
+  bool Deleted = false;
+  /// Whether every function invoked by the implicit definition is constexpr.
+  bool Constexpr = true;
+};
+} // namespace
+
+/// Get the type of the first parameter of a postfix increment or decrement
+/// operator function, where the implicit object parameter (if any) is
+/// considered to be the first parameter.
+static QualType getPostfixOperatorFirstParamType(const FunctionDecl *FD) {
+  if (const auto *MD = dyn_cast<CXXMethodDecl>(FD))
+    return MD->getFunctionObjectParameterReferenceType();
+  return FD->getParamDecl(0)->getType();
+}
+
+/// Get the type C for which a well-formed defaulted postfix increment or
+/// decrement operator function is defaulted.
+static QualType getDefaultedPostfixOperatorType(const FunctionDecl *FD) {
+  return getPostfixOperatorFirstParamType(FD)
+      .getNonReferenceType()
+      .getUnqualifiedType();
+}
+
+/// Determine whether a defaulted postfix increment or decrement operator
+/// function for type \p C should be defined as deleted, as specified in C++2d
+/// [over.inc.default]p2, and whether every function it would invoke is
+/// constexpr.
+static DefaultedPostfixOperatorInfo
+analyzeDefaultedPostfixOperator(Sema &S, FunctionDecl *FD,
+                                PostfixOperatorKind Kind, QualType C,
+                                PostfixOperatorDiagnoseKind Diagnose) {
+  using DiagnoseKind = PostfixOperatorDiagnoseKind;
+  DefaultedPostfixOperatorInfo Info;
+  SourceLocation Loc = FD->getLocation();
+
+  // C++2d [over.inc.default]p1:
+  //   Name lookups and access checks in the implicit definition of a defaulted
+  //   postfix increment or decrement operator function are performed from a
+  //   context equivalent to its function-body.
+  Sema::ContextRAII SavedContext(S, FD);
+
+  // Let c be an lvalue of type C.
+  OpaqueValueExpr Obj(Loc, C, VK_LValue);
+  Expr *Args[] = {&Obj};
+
+  auto Deleted = [&] {
+    Info.Deleted = true;
+    return Info;
+  };
+  auto InaccessibleDiag = [&](PostfixOperatorStep Step) {
+    return Diagnose == DiagnoseKind::ExplainDeleted
+               ? S.PDiag(diag::note_defaulted_postfix_operator_inaccessible)
+                     << FD << (int)Step
+               : S.PDiag();
+  };
+  auto NoteCallsDeleted = [&](PostfixOperatorStep Step, FunctionDecl *Callee) {
+    if (Diagnose != DiagnoseKind::ExplainDeleted)
+      return;
+    S.Diag(Loc, diag::note_defaulted_postfix_operator_calls_deleted)
+        << FD << (int)Step;
+    if (Callee)
+      S.NoteDeletedFunction(Callee);
+  };
+  auto CheckConstexpr = [&](const FunctionDecl *Callee,
+                            PostfixOperatorStep Step) {
+    if (Callee->isConstexpr())
+      return;
+    Info.Constexpr = false;
+    if (Diagnose == DiagnoseKind::ExplainConstexpr)
+      S.Diag(Callee->getLocation(),
+             diag::note_defaulted_postfix_operator_not_constexpr_here)
+          << (int)Step;
+  };
+
+  // C++2d [over.inc.default]p2:
+  //   A defaulted postfix increment or decrement operator function for a type
+  //   C is defined as deleted if
+  CXXRecordDecl *RD = C->getAsCXXRecordDecl();
+  if (RD) {
+    //  -- C is a class type for which overload resolution, as applied to
+    //     direct-initialization of a variable of type C from an lvalue of
+    //     type C, does not result in a usable candidate,
+    InitializedEntity Entity = InitializedEntity::InitializeTemporary(C);
+    InitializationKind InitKind =
+        InitializationKind::CreateDirect(Loc, Loc, Loc);
+    InitializationSequence Seq(S, Entity, InitKind, Args);
+    if (Seq.Failed()) {
+      if (Diagnose == DiagnoseKind::ExplainDeleted) {
+        OverloadCandidateSet &Cands = Seq.getFailedCandidateSet();
+        OverloadingResult OR =
+            Seq.getFailureKind() ==
+                    InitializationSequence::FK_ConstructorOverloadFailed
+                ? Seq.getFailedOverloadResult()
+                : OR_No_Viable_Function;
+        OverloadCandidateSet::iterator Best;
+        switch (OR) {
+        case OR_Deleted:
+          NoteCallsDeleted(PostfixOperatorStep::Constructor,
+                           Cands.BestViableFunction(S, Loc, Best) == OR_Deleted
+                               ? Best->Function
+                               : nullptr);
+          break;
+        case OR_Ambiguous:
+          Cands.NoteCandidates(
+              PartialDiagnosticAt(
+                  Loc, S.PDiag(diag::note_defaulted_postfix_operator_ambiguous)
+                           << FD << 0 << C),
+              S, OCD_AmbiguousCandidates, Args, /*Opc=*/"", Loc);
+          break;
+        case OR_Success:
+        case OR_No_Viable_Function:
+          Cands.NoteCandidates(
+              PartialDiagnosticAt(
+                  Loc, S.PDiag(diag::note_defaulted_postfix_operator_no_viable)
+                           << FD << 0 << C),
+              S, OCD_AllCandidates, Args, /*Opc=*/"", Loc);
+          break;
+        }
+      }
+      return Deleted();
+    }
+    for (const InitializationSequence::Step &Step : Seq.steps()) {
+      if (Step.Kind != InitializationSequence::SK_ConstructorInitialization)
+        continue;
+      if (!S.isMemberAccessibleForDeletion(
+              RD, Step.Function.FoundDecl, C, Loc,
+              InaccessibleDiag(PostfixOperatorStep::Constructor)))
+        return Deleted();
+      CheckConstexpr(Step.Function.Function, PostfixOperatorStep::Constructor);
+    }
+
+    //  -- C has a destructor that is deleted or inaccessible from the context
+    //     of the function-body, or
+    if (CXXDestructorDecl *Dtor = S.LookupDestructor(RD)) {
+      if (Dtor->isDeleted()) {
+        NoteCallsDeleted(PostfixOperatorStep::Destructor, Dtor);
+        return Deleted();
+      }
+      if (!S.isMemberAccessibleForDeletion(
+              RD, DeclAccessPair::make(Dtor, Dtor->getAccess()), C, Loc,
+              InaccessibleDiag(PostfixOperatorStep::Destructor)))
+        return Deleted();
+    }
+  }
+
+  //  -- for an lvalue c of type C, overload resolution as applied to ++c for a
+  //     postfix increment operator function or --c for a postfix decrement
+  //     operator function does not result in a usable candidate.
+  bool IsIncrement = Kind == PostfixOperatorKind::Increment;
+  PostfixOperatorStep PrefixStep = IsIncrement
+                                       ? PostfixOperatorStep::PrefixIncrement
+                                       : PostfixOperatorStep::PrefixDecrement;
+  UnresolvedSet<16> Fns;
+  if (auto *DFI = FD->getDefaultedOrDeletedInfo())
+    Fns.assign(DFI->getUnqualifiedLookups().begin(),
+               DFI->getUnqualifiedLookups().end());
+  OverloadCandidateSet CandidateSet(Loc, OverloadCandidateSet::CSK_Operator);
+  S.LookupOverloadedUnaryOp(
+      CandidateSet, IsIncrement ? OO_PlusPlus : OO_MinusMinus, Fns, Args);
+
+  OverloadCandidateSet::iterator Best;
+  switch (CandidateSet.BestViableFunction(S, Loc, Best)) {
+  case OR_Success:
+    if (FunctionDecl *Prefix = Best->Function) {
+      // Only a member of C can be inaccessible here; the non-member
+      // candidates were found by unqualified lookup and ADL.
+      if (RD && Best->FoundDecl.getDecl() &&
+          Best->FoundDecl.getDecl()->isCXXClassMember() &&
+          !S.isMemberAccessibleForDeletion(RD, Best->FoundDecl, C, Loc,
+                                           InaccessibleDiag(PrefixStep)))
+        return Deleted();
+      CheckConstexpr(Prefix, PrefixStep);
+    } else if (!Best->Conversions.empty() &&
+               Best->Conversions[0].isUserDefined()) {
+      // A built-in candidate, reached through a conversion function.
+      CheckConstexpr(Best->Conversions[0].UserDefined.ConversionFunction,
+                     PostfixOperatorStep::ConversionFunction);
+    }
+    break;
+
+  case OR_Ambiguous:
+    if (Diagnose == DiagnoseKind::ExplainDeleted)
+      CandidateSet.NoteCandidates(
+          PartialDiagnosticAt(
+              Loc, S.PDiag(diag::note_defaulted_postfix_operator_ambiguous)
+                       << FD << (int)Kind << C),
+          S, OCD_AmbiguousCandidates, Args, IsIncrement ? "++" : "--", Loc);
+    return Deleted();
+
+  case OR_Deleted:
+    NoteCallsDeleted(PrefixStep, Best->Function);
+    return Deleted();
+
+  case OR_No_Viable_Function:
+    // Don't list the non-viable candidates: like for any other unary operator,
+    // the candidate notes can't sensibly describe an arity mismatch for a
+    // member postfix operator (whose object argument is not a parameter).
+    if (Diagnose == DiagnoseKind::ExplainDeleted)
+      S.Diag(Loc, diag::note_defaulted_postfix_operator_no_viable)
+          << FD << (int)Kind << C;
+    return Deleted();
+  }
+
+  return Info;
+}
+
+/// Build the body of a defaulted postfix increment or decrement operator
+/// function. C++2d [over.inc.default]p3:
+///   The implicit definition of a defaulted postfix increment or decrement
+///   operator function F that is not defined as deleted for a type C is
+///   equivalent to:
+///     C tmp(c);
+///     ++c;      // or --c
+///     return tmp;
+///   where tmp is a variable defined for exposition only, and c is an lvalue
+///   that denotes *this if F is an implicit object member function, or the
+///   first parameter of F otherwise.
+static StmtResult buildDefaultedPostfixOperatorBody(Sema &S, FunctionDecl *FD,
+                                                    PostfixOperatorKind Kind,
+                                                    SourceLocation Loc) {
+  Sema::CompoundScopeRAII CompoundScope(S);
+
+  // Build an expression denoting 'c'.
+  auto BuildObject = [&]() -> ExprResult {
+    if (const auto *MD = dyn_cast<CXXMethodDecl>(FD);
+        MD && MD->isImplicitObjectMemberFunction()) {
+      ExprResult This = S.ActOnCXXThis(Loc);
+      if (This.isInvalid())
+        return ExprError();
+      return S.CreateBuiltinUnaryOp(Loc, UO_Deref, This.get());
+    }
+    ParmVarDecl *Param = FD->getParamDecl(0);
+    return S.BuildDeclarationNameExpr(
+        CXXScopeSpec(), DeclarationNameInfo(Param->getDeclName(), Loc), Param);
+  };
+
+  // C tmp(c);
+  ExprResult Init = BuildObject();
+  if (Init.isInvalid())
+    return StmtError();
+  QualType C = FD->getReturnType();
+  IdentifierInfo *TmpName = &S.Context.Idents.get("tmp");
+  VarDecl *Tmp =
+      VarDecl::Create(S.Context, S.CurContext, Loc, Loc, TmpName, C,
+                      S.Context.getTrivialTypeSourceInfo(C, Loc), SC_None);
+  Expr *InitArgs[] = {Init.get()};
+  S.AddInitializerToDecl(Tmp,
+                         ParenListExpr::Create(S.Context, Loc, InitArgs, Loc),
+                         /*DirectInit=*/true);
+  if (Tmp->isInvalidDecl())
+    return StmtError();
+  StmtResult TmpStmt = S.ActOnDeclStmt(S.ConvertDeclToDeclGroup(Tmp), Loc, Loc);
+  if (TmpStmt.isInvalid())
+    return StmtError();
+
+  // ++c; or --c;
+  ExprResult Obj = BuildObject();
+  if (Obj.isInvalid())
+    return StmtError();
+  UnaryOperatorKind Opc =
+      Kind == PostfixOperatorKind::Increment ? UO_PreInc : UO_PreDec;
+  ExprResult Prefix;
+  if (Obj.get()->getType()->isOverloadableType()) {
+    UnresolvedSet<16> Fns;
+    if (auto *DFI = FD->getDefaultedOrDeletedInfo())
+      Fns.assign(DFI->getUnqualifiedLookups().begin(),
+                 DFI->getUnqualifiedLookups().end());
+    Prefix = S.CreateOverloadedUnaryOp(Loc, Opc, Fns, Obj.get(),
+                                       /*RequiresADL=*/true);
+  } else {
+    Prefix = S.CreateBuiltinUnaryOp(Loc, Opc, Obj.get());
+  }
+  if (Prefix.isInvalid())
+    return StmtError();
+  StmtResult PrefixStmt = S.ActOnExprStmt(Prefix, /*DiscardedValue=*/true);
+  if (PrefixStmt.isInvalid())
+    return StmtError();
+
+  // return tmp;
+  ExprResult TmpRef = S.BuildDeclarationNameExpr(
+      CXXScopeSpec(), DeclarationNameInfo(TmpName, Loc), Tmp);
+  if (TmpRef.isInvalid())
+    return StmtError();
+  StmtResult Return = S.BuildReturnStmt(Loc, TmpRef.get());
+  if (Return.isInvalid())
+    return StmtError();
+  // 'tmp' is the only variable returned by this function, so it qualifies for
+  // the named return value optimization.
+  if (cast<ReturnStmt>(Return.get())->getNRVOCandidate() == Tmp)
+    Tmp->setNRVOVariable(true);
+
+  Stmt *Stmts[] = {TmpStmt.get(), PrefixStmt.get(), Return.get()};
+  return S.ActOnCompoundStmt(Loc, Loc, Stmts, /*IsStmtExpr=*/false);
+}
+
+bool Sema::CheckExplicitlyDefaultedPostfixOperator(Scope *S, FunctionDecl *FD,
+                                                   PostfixOperatorKind Kind) {
+  assert(Kind != PostfixOperatorKind::None &&
+         "not a defaulted postfix operator");
+  bool IsIncrement = Kind == PostfixOperatorKind::Increment;
+
+  // Perform the unqualified lookup of the prefix operator that the implicit
+  // definition needs. C++2d [over.inc.default]p1:
+  //   Name lookups and access checks in the implicit definition of a defaulted
+  //   postfix increment or decrement operator function are performed from a
+  //   context equivalent to its function-body.
+  if (S) {
+    UnresolvedSet<16> Operators;
+    LookupOverloadedOperatorName(IsIncrement ? OO_PlusPlus : OO_MinusMinus, S,
+                                 Operators);
+    FD->setDefaultedOrDeletedInfo(
+        FunctionDecl::DefaultedOrDeletedFunctionInfo::Create(
+            Context, Operators.pairs(), CurFPFeatureOverrides()));
+  }
+
+  // C++2d [over.inc.default]p1:
+  //   A defaulted postfix increment or decrement operator function for a type
+  //   C shall be a non-template function that
+  //    -- has a first parameter of type "reference to C" or a first parameter
+  //       of type "reference to volatile C", where the implicit object
+  //       parameter (if any) is considered to be the first parameter,
+  //    -- is defined as defaulted in C or in a context where C is complete,
+  //       and
+  //    -- has a declared return type of C.
+  auto *MD = dyn_cast<CXXMethodDecl>(FD);
+  ParmVarDecl *FirstParam = FD->getParamDecl(0);
+  QualType FirstParamType = getPostfixOperatorFirstParamType(FD);
+  QualType ReturnType = FD->getDeclaredReturnType();
+
+  // Determine the type C. For a member function, this is the enclosing class.
+  // For a non-member function, infer it from the first parameter, falling back
+  // to the return type if the first parameter is not a reference.
+  QualType C;
+  if (MD)
+    C = Context.getCanonicalTagType(MD->getParent());
+  else if (FirstParamType->isReferenceType())
+    C = FirstParamType.getNonReferenceType().getUnqualifiedType();
+  else if (!ReturnType->isReferenceType())
+    C = ReturnType.getUnqualifiedType();
+  if (C.isNull() || (!MD && !C->isDependentType() && !C->isRecordType() &&
+                     !C->isEnumeralType())) {
+    Diag(FirstParam->getBeginLoc(),
+         diag::err_defaulted_postfix_operator_param_unknown)
+        << (int)Kind << FirstParamType << FirstParam->getSourceRange();
+    return true;
+  }
+
+  // Check the type of the first parameter.
+  if (!FirstParamType->isDependentType() && !C->isDependentType()) {
+    QualType Referent = FirstParamType->isReferenceType()
+                            ? FirstParamType.getNonReferenceType()
+                            : QualType();
+    if (Referent.isNull() || Referent.isConstQualified() ||
+        !Context.hasSameType(Referent.getUnqualifiedType(), C)) {
+      if (MD && MD->isImplicitObjectMemberFunction()) {
+        // The implicit object parameter is always a reference to the class,
+        // so the only possible problem is a const qualifier.
+        Diag(MD->getLocation(), diag::err_defaulted_postfix_operator_const)
+            << (int)Kind;
+      } else {
+        Diag(FirstParam->getBeginLoc(),
+             diag::err_defaulted_postfix_operator_param)
+            << (int)Kind << !MD << FirstParamType
+            << Context.getLValueReferenceType(C)
+            << Context.getLValueReferenceType(C.withVolatile())
+            << FirstParam->getSourceRange();
+      }
+      return true;
+    }
+  }
+
+  // Check the declared return type. A placeholder type is not permitted; this
+  // would need P2952R2 (auto& operator=(X&&) = default), which is not yet
+  // part of the standard.
+  if (ReturnType->getContainedDeducedType() ||
+      (!ReturnType->isDependentType() && !C->isDependentType() &&
+       !Context.hasSameType(ReturnType, C))) {
+    Diag(FD->getLocation(), diag::err_defaulted_postfix_operator_return_type)
+        << (int)Kind << C << ReturnType << FD->getReturnTypeSourceRange();
+    return true;
+  }
+
+  // For a defaulted function in a dependent context, defer all remaining
+  // checks until instantiation.
+  if (C->isDependentType() || FD->isDependentContext())
+    return false;
+
+  //    -- is defined as defaulted in C or in a context where C is complete
+  if (RequireCompleteType(FD->getLocation(), C,
+                          diag::err_defaulted_postfix_operator_incomplete,
+                          (int)Kind))
+    return true;
+
+  // Determine whether the function should be defined as deleted.
+  DefaultedPostfixOperatorInfo Info = analyzeDefaultedPostfixOperator(
+      *this, FD, Kind, C, PostfixOperatorDiagnoseKind::NoDiagnostics);
+
+  bool First = FD == FD->getCanonicalDecl();
+
+  if (!First) {
+    if (Info.Deleted) {
+      // C++11 [dcl.fct.def.default]p4:
+      //   [For a] user-provided explicitly-defaulted function [...] if such a
+      //   function is implicitly defined as deleted, the program is ill-formed.
+      //
+      // This is really just a consequence of the general rule that you can
+      // only delete a function on its first declaration.
+      Diag(FD->getLocation(),
+           diag::err_non_first_default_postfix_operator_deletes)
+          << (int)Kind;
+      analyzeDefaultedPostfixOperator(
+          *this, FD, Kind, C, PostfixOperatorDiagnoseKind::ExplainDeleted);
+      return true;
+    }
+    if (isa<CXXRecordDecl>(FD->getLexicalDeclContext())) {
+      // C++2d [over.inc.default]p1:
+      //   A definition of a postfix increment or decrement operator as
+      //   defaulted that appears in a class shall be the first declaration of
+      //   that function.
+      Diag(FD->getLocation(),
+           diag::err_non_first_default_postfix_operator_in_class)
+          << (int)Kind;
+      Diag(FD->getCanonicalDecl()->getLocation(),
+           diag::note_previous_declaration);
+      return true;
+    }
+  }
+
+  // If we want to delete the function, then do so; there's nothing else to
+  // check in that case.
+  if (Info.Deleted) {
+    SetDeclDeleted(FD, FD->getLocation());
+    if (!inTemplateInstantiation() && !FD->isImplicit()) {
+      Diag(FD->getLocation(), diag::warn_defaulted_postfix_operator_deleted)
+          << (int)Kind;
+      analyzeDefaultedPostfixOperator(
+          *this, FD, Kind, C, PostfixOperatorDiagnoseKind::ExplainDeleted);
+      if (FD->getDefaultLoc().isValid())
+        Diag(FD->getDefaultLoc(), diag::note_replace_equals_default_to_delete)
+            << FixItHint::CreateReplacement(FD->getDefaultLoc(), "delete");
+    }
+    return false;
+  }
+
+  // C++2a [dcl.fct.def.default]p3 [P2002R0]:
+  //   An explicitly-defaulted function that is not defined as deleted may be
+  //   declared constexpr or consteval only if it is constexpr-compatible.
+  // By analogy with C++2a [class.compare.default]p3, we consider a defaulted
+  // postfix operator to be constexpr-compatible if its parameter and return
+  // types are literal types and no overload resolution performed when
+  // determining whether to delete it results in a non-constexpr function.
+  //
+  // C++23 [dcl.fct.def.default]p3 [P2448R2] removed this restriction: a
+  // defaulted function may always be declared constexpr.
+  if (FD->isConstexpr() && !getLangOpts().CPlusPlus23 &&
+      !FD->isTemplateInstantiation() &&
+      CheckConstexprReturnType(*this, FD, CheckConstexprKind::Diagnose) &&
+      CheckConstexprParameterTypes(*this, FD, CheckConstexprKind::Diagnose) &&
+      !Info.Constexpr) {
+    Diag(FD->getBeginLoc(),
+         diag::err_defaulted_postfix_operator_constexpr_mismatch)
+        << (int)Kind << FD->isConsteval();
+    analyzeDefaultedPostfixOperator(
+        *this, FD, Kind, C, PostfixOperatorDiagnoseKind::ExplainConstexpr);
+    return true;
+  }
+
+  if (First) {
+    // C++11 [dcl.fct.def.default]p5:
+    //   A function explicitly defaulted on its first declaration is implicitly
+    //   inline.
+    FD->setImplicitlyInline();
+
+    // C++2a [dcl.fct.def.default]p3 [P2002R0]:
+    //   If a constexpr-compatible function is explicitly defaulted on its
+    //   first declaration, it is implicitly considered to be constexpr.
+    // C++23 [dcl.fct.def.default]p3 [P2448R2]:
+    //   A function explicitly defaulted on its first declaration is [...]
+    //   implicitly constexpr if it is constexpr-suitable.
+    // A postfix operator function is always constexpr-suitable. The implicit
+    // definition declares a variable, so it can only be constexpr in C++14
+    // and later, and a virtual function can only be constexpr in C++20 and
+    // later.
+    if (!FD->isConstexpr() && getLangOpts().CPlusPlus14 &&
+        (getLangOpts().CPlusPlus20 || !MD || !MD->isVirtual()) &&
+        (getLangOpts().CPlusPlus23 ||
+         (Info.Constexpr &&
+          CheckConstexprReturnType(*this, FD, CheckConstexprKind::CheckValid) &&
+          CheckConstexprParameterTypes(*this, FD,
+                                       CheckConstexprKind::CheckValid))))
+      FD->setConstexprKind(ConstexprSpecKind::Constexpr);
+
+    // C++2d [except.spec]p10:
+    //   The exception specification for [...] a postfix increment or decrement
+    //   operator function without a noexcept-specifier that is defaulted on
+    //   its first declaration is potentially-throwing if and only if any
+    //   expression in the implicit definition is potentially-throwing.
+    if (FD->getExceptionSpecType() == EST_None) {
+      auto *FPT = FD->getType()->castAs<FunctionProtoType>();
+      FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
+      EPI.ExceptionSpec.Type = EST_Unevaluated;
+      EPI.ExceptionSpec.SourceDecl = FD;
+      FD->setType(Context.getFunctionType(FPT->getReturnType(),
+                                          FPT->getParamTypes(), EPI));
+    }
+  }
+
+  return false;
+}
+
+void Sema::DefineDefaultedPostfixOperator(SourceLocation UseLoc,
+                                          FunctionDecl *FD,
+                                          PostfixOperatorKind Kind) {
+  assert(FD->isDefaulted() && !FD->isDeleted() &&
+         !FD->doesThisDeclarationHaveABody());
+  if (FD->willHaveBody() || FD->isInvalidDecl())
+    return;
+
+  SynthesizedFunctionScope Scope(*this, FD);
+
+  // Add a context note for diagnostics produced after this point.
+  Scope.addContextNote(UseLoc);
+
+  DefaultedFunctionFPFeaturesRAII RestoreFP(*this, FD);
+
+  {
+    // Build and set up the function body.
+    SourceLocation BodyLoc =
+        FD->getEndLoc().isValid() ? FD->getEndLoc() : FD->getLocation();
+    StmtResult Body =
+        buildDefaultedPostfixOperatorBody(*this, FD, Kind, BodyLoc);
+    if (Body.isInvalid()) {
+      FD->setInvalidDecl();
+      return;
+    }
+    FD->setBody(Body.get());
+    FD->markUsed(Context);
+  }
+
+  // The exception specification is needed because we are defining the
+  // function. Note that this will reuse the body we just built.
+  ResolveExceptionSpec(UseLoc, FD->getType()->castAs<FunctionProtoType>());
+
+  if (ASTMutationListener *L = getASTMutationListener())
+    L->CompletedImplicitDefinition(FD);
+}
+
+static Sema::ImplicitExceptionSpecification
+ComputeDefaultedPostfixOperatorExceptionSpec(Sema &S, SourceLocation Loc,
+                                             FunctionDecl *FD,
+                                             PostfixOperatorKind Kind) {
+  ComputingExceptionSpec CES(S, FD, Loc);
+  Sema::ImplicitExceptionSpecification ExceptSpec(S);
+
+  if (FD->isInvalidDecl())
+    return ExceptSpec;
+
+  // The common case is that we just defined the function. In that case, just
+  // look at whether the body can throw.
+  if (Stmt *FunctionBody = FD->getBody()) {
+    ExceptSpec.CalledStmt(FunctionBody);
+  } else {
+    // Otherwise, build a body so we can check it. Pretend to synthesize the
+    // function body in an unevaluated context: we are not permitted to mark
+    // its callees as referenced.
+    Sema::SynthesizedFunctionScope Scope(S, FD);
+    EnterExpressionEvaluationContext Context(
+        S, Sema::ExpressionEvaluationContext::Unevaluated);
+
+    SourceLocation BodyLoc =
+        FD->getEndLoc().isValid() ? FD->getEndLoc() : FD->getLocation();
+    StmtResult Body = buildDefaultedPostfixOperatorBody(S, FD, Kind, BodyLoc);
+    if (!Body.isInvalid())
+      ExceptSpec.CalledStmt(Body.get());
+  }
+
+  // The implicit definition also destroys the temporary copy.
+  if (CXXRecordDecl *RD = FD->getReturnType()->getAsCXXRecordDecl())
+    if (CXXDestructorDecl *Dtor = S.LookupDestructor(RD))
+      ExceptSpec.CalledDecl(Loc, Dtor);
+
+  return ExceptSpec;
+}
+
 void Sema::CheckDelayedMemberExceptionSpecs() {
   decltype(DelayedOverridingExceptionSpecChecks) Overriding;
   decltype(DelayedEquivalentExceptionSpecChecks) Equivalent;
@@ -10160,6 +10792,10 @@ void Sema::DiagnoseDeletedDefaultedFunction(FunctionDecl *FD) {
   if (DFK.isSpecialMember()) {
     ShouldDeleteSpecialMember(cast<CXXMethodDecl>(FD), DFK.asSpecialMember(),
                               nullptr, /*Diagnose=*/true);
+  } else if (DFK.isPostfixOperator()) {
+    analyzeDefaultedPostfixOperator(
+        *this, FD, DFK.asPostfixOperator(), getDefaultedPostfixOperatorType(FD),
+        PostfixOperatorDiagnoseKind::ExplainDeleted);
   } else {
     DefaultedComparisonAnalyzer(
         *this, cast<CXXRecordDecl>(FD->getLexicalDeclContext()), FD,
@@ -18952,21 +19588,35 @@ void Sema::SetDeclDeleted(Decl *Dcl, SourceLocation DelLoc,
   Fn->setDeletedAsWritten(true, Message);
 }
 
-void Sema::SetDeclDefaulted(Decl *Dcl, SourceLocation DefaultLoc) {
+void Sema::SetDeclDefaulted(Decl *Dcl, SourceLocation DefaultLoc, Scope *S) {
   if (!Dcl || Dcl->isInvalidDecl())
     return;
 
+  // The %select index for err_default_special_members: which kinds of
+  // functions can be defaulted in the current language mode.
+  unsigned DefaultableKinds = 0;
+  if (getLangOpts().CPlusPlus29)
+    DefaultableKinds = 2;
+  else if (getLangOpts().CPlusPlus20)
+    DefaultableKinds = 1;
+
   auto *FD = dyn_cast<FunctionDecl>(Dcl);
   if (!FD) {
     if (auto *FTD = dyn_cast<FunctionTemplateDecl>(Dcl)) {
-      if (FTD->getTemplatedDecl()->getDefaultedFunctionKind().isComparison()) {
+      FunctionDecl::DefaultedFunctionKind DFK =
+          FTD->getTemplatedDecl()->getDefaultedFunctionKind();
+      if (DFK.isComparison()) {
         Diag(DefaultLoc, diag::err_defaulted_comparison_template);
         return;
       }
+      if (DFK.isPostfixOperator()) {
+        Diag(DefaultLoc, diag::err_defaulted_postfix_operator_template)
+            << (int)DFK.asPostfixOperator();
+        return;
+      }
     }
 
-    Diag(DefaultLoc, diag::err_default_special_members)
-        << getLangOpts().CPlusPlus20;
+    Diag(DefaultLoc, diag::err_default_special_members) << DefaultableKinds;
     return;
   }
 
@@ -18979,8 +19629,12 @@ void Sema::SetDeclDefaulted(Decl *Dcl, SourceLocation DefaultLoc) {
       (!FD->isDependentContext() ||
        (!isa<CXXConstructorDecl>(FD) &&
         FD->getDeclName().getCXXOverloadedOperator() != OO_Equal))) {
-    Diag(DefaultLoc, diag::err_default_special_members)
-        << getLangOpts().CPlusPlus20;
+    OverloadedOperatorKind OO = FD->getDeclName().getCXXOverloadedOperator();
+    if (OO == OO_PlusPlus || OO == OO_MinusMinus)
+      Diag(DefaultLoc, diag::err_defaulted_prefix_operator)
+          << (OO == OO_MinusMinus);
+    else
+      Diag(DefaultLoc, diag::err_default_special_members) << DefaultableKinds;
     return;
   }
 
@@ -18990,6 +19644,10 @@ void Sema::SetDeclDefaulted(Decl *Dcl, SourceLocation DefaultLoc) {
       DefKind.asComparison() != DefaultedComparisonKind::ThreeWay) {
     DiagCompat(DefaultLoc, diag_compat::defaulted_comparison);
   }
+  // Defaulted postfix increment and decrement operators (P3668R4) are a C++2d
+  // feature that we support in all C++ language modes as an extension.
+  if (DefKind.isPostfixOperator() && !inTemplateInstantiation())
+    DiagCompat(DefaultLoc, diag_compat::defaulted_postfix_operator);
 
   FD->setDefaulted();
   FD->setExplicitlyDefaulted();
@@ -19004,8 +19662,8 @@ void Sema::SetDeclDefaulted(Decl *Dcl, SourceLocation DefaultLoc) {
   // that we've marked it as defaulted.
   FD->setWillHaveBody(false);
 
-  if (DefKind.isComparison()) {
-    // If this comparison's defaulting occurs within the definition of its
+  if (DefKind.isComparison() || DefKind.isPostfixOperator()) {
+    // If this function's defaulting occurs within the definition of its
     // lexical class context, we have to do the checking when complete.
     if (auto const *RD = dyn_cast<CXXRecordDecl>(FD->getLexicalDeclContext()))
       if (!RD->isCompleteDefinition())
@@ -19040,6 +19698,13 @@ void Sema::SetDeclDefaulted(Decl *Dcl, SourceLocation DefaultLoc) {
       FD->setInvalidDecl();
     else
       DefineDefaultedComparison(DefaultLoc, FD, DefKind.asComparison());
+  } else if (DefKind.isPostfixOperator()) {
+    if (CheckExplicitlyDefaultedPostfixOperator(S, FD,
+                                                DefKind.asPostfixOperator()))
+      FD->setInvalidDecl();
+    else if (!FD->isDeleted())
+      DefineDefaultedPostfixOperator(DefaultLoc, FD,
+                                     DefKind.asPostfixOperator());
   } else {
     auto *MD = cast<CXXMethodDecl>(FD);
 
@@ -19071,13 +19736,13 @@ void Sema::DiagnoseReturnInConstructorExceptionHandler(CXXTryStmt *TryBlock) {
 }
 
 void Sema::SetFunctionBodyKind(Decl *D, SourceLocation Loc, FnBodyKind BodyKind,
-                               StringLiteral *DeletedMessage) {
+                               StringLiteral *DeletedMessage, Scope *S) {
   switch (BodyKind) {
   case FnBodyKind::Delete:
     SetDeclDeleted(D, Loc, DeletedMessage);
     break;
   case FnBodyKind::Default:
-    SetDeclDefaulted(D, Loc);
+    SetDeclDefaulted(D, Loc, S);
     break;
   case FnBodyKind::Other:
     llvm_unreachable(
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 3258abbc4d24f0..0eac93e34d1ba4 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -19321,9 +19321,12 @@ void Sema::MarkFunctionReferenced(SourceLocation Loc, FunctionDecl *Func,
       }
 
       if (Func->isDefaulted() && !Func->isDeleted()) {
-        DefaultedComparisonKind DCK = Func->getDefaultedComparisonKind();
-        if (DCK != DefaultedComparisonKind::None)
-          DefineDefaultedComparison(Loc, Func, DCK);
+        FunctionDecl::DefaultedFunctionKind DFK =
+            Func->getDefaultedFunctionKind();
+        if (DFK.isComparison())
+          DefineDefaultedComparison(Loc, Func, DFK.asComparison());
+        else if (DFK.isPostfixOperator())
+          DefineDefaultedPostfixOperator(Loc, Func, DFK.asPostfixOperator());
       }
 
       // Implicit instantiation of function templates and member functions of
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index c19a47c35639c5..819e8566c67818 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -15502,6 +15502,19 @@ Sema::CreateOverloadedUnaryOp(SourceLocation OpLoc, UnaryOperatorKind Opc,
     return ExprError();
 
   case OR_Deleted: {
+    // A defaulted postfix increment or decrement operator that was implicitly
+    // defined as deleted. Explain why it was deleted.
+    if (FunctionDecl *DeletedFD = Best->Function;
+        DeletedFD->isDefaulted() &&
+        DeletedFD->getDefaultedFunctionKind().isPostfixOperator()) {
+      Diag(OpLoc, diag::err_ovl_deleted_postfix_operator)
+          << Input->getType()
+          << (int)DeletedFD->getDefaultedFunctionKind().asPostfixOperator()
+          << Input->getSourceRange();
+      NoteDeletedFunction(DeletedFD);
+      return ExprError();
+    }
+
     // CreateOverloadedUnaryOp fills the first element of ArgsArray with the
     // object whose method was called. Later in NoteCandidates size of ArgsArray
     // is passed further and it eventually ends up compared to number of
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index 06ea12ca7bd73b..fb51c8a95c40da 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -1168,6 +1168,13 @@ void Sema::PrintInstantiationStack(InstantiationContextDiagFuncRef DiagFunc) {
         DiagFunc(Active->PointOfInstantiation,
                  PDiag(diag::note_comparison_synthesized_at)
                      << (int)DFK.asComparison() << RecordType);
+      } else if (DFK.isPostfixOperator()) {
+        // The declared return type of a defaulted postfix operator is the
+        // type for which it is defaulted.
+        DiagFunc(Active->PointOfInstantiation,
+                 PDiag(diag::note_postfix_operator_synthesized_at)
+                     << (int)DFK.asPostfixOperator()
+                     << FD->getReturnType().getUnqualifiedType());
       }
       break;
     }
diff --git a/clang/test/AST/ast-dump-defaulted-postfix-operators.cpp b/clang/test/AST/ast-dump-defaulted-postfix-operators.cpp
new file mode 100644
index 00000000000000..3b9bec55202137
--- /dev/null
+++ b/clang/test/AST/ast-dump-defaulted-postfix-operators.cpp
@@ -0,0 +1,92 @@
+// RUN: %clang_cc1 -std=c++2d -ast-dump %s | FileCheck %s
+
+// The implicit definition of a defaulted postfix increment or decrement
+// operator for a type C is equivalent to:
+//   C tmp(c);
+//   ++c;
+//   return tmp;
+
+struct S {
+  int v;
+  S &operator++();
+  S operator++(int) = default;
+};
+void use_member(S s) { s++; }
+
+// CHECK:      CXXMethodDecl {{.*}} used constexpr operator++ 'S (int) noexcept(false)' default implicit-inline
+// CHECK-NEXT:   ParmVarDecl {{.*}} 'int'
+// CHECK-NEXT:   CompoundStmt
+// CHECK-NEXT:     DeclStmt
+// CHECK-NEXT:       VarDecl {{.*}} used tmp 'S' nrvo callinit
+// CHECK-NEXT:         CXXConstructExpr {{.*}} 'S' 'void (const S &) noexcept'
+// CHECK-NEXT:           ImplicitCastExpr {{.*}} 'const S' lvalue <NoOp>
+// CHECK-NEXT:             UnaryOperator {{.*}} 'S' lvalue prefix '*' cannot overflow
+// CHECK-NEXT:               CXXThisExpr {{.*}} 'S *' this
+// CHECK-NEXT:     CXXOperatorCallExpr {{.*}} 'S' lvalue '++'
+// CHECK-NEXT:       ImplicitCastExpr {{.*}} 'S &(*)()' <FunctionToPointerDecay>
+// CHECK-NEXT:         DeclRefExpr {{.*}} 'S &()' lvalue CXXMethod {{.*}} 'operator++' 'S &()'
+// CHECK-NEXT:       UnaryOperator {{.*}} 'S' lvalue prefix '*' cannot overflow
+// CHECK-NEXT:         CXXThisExpr {{.*}} 'S *' this
+// CHECK-NEXT:     ReturnStmt {{.*}} nrvo_candidate(Var {{.*}} 'tmp' 'S')
+// CHECK-NEXT:       CXXConstructExpr {{.*}} 'S' 'void (S &&) noexcept'
+// CHECK-NEXT:         ImplicitCastExpr {{.*}} 'S' xvalue <NoOp>
+// CHECK-NEXT:           DeclRefExpr {{.*}} 'S' lvalue Var {{.*}} 'tmp' 'S'
+
+struct T {
+  int v;
+  T &operator--();
+};
+T operator--(T &self, int) = default;
+void use_non_member(T t) { t--; }
+
+// CHECK:      FunctionDecl {{.*}} used constexpr operator-- 'T (T &, int) noexcept(false)' default implicit-inline
+// CHECK-NEXT:   ParmVarDecl {{.*}} used self 'T &'
+// CHECK-NEXT:   ParmVarDecl {{.*}} 'int'
+// CHECK-NEXT:   CompoundStmt
+// CHECK-NEXT:     DeclStmt
+// CHECK-NEXT:       VarDecl {{.*}} used tmp 'T' nrvo callinit
+// CHECK-NEXT:         CXXConstructExpr {{.*}} 'T' 'void (const T &) noexcept'
+// CHECK-NEXT:           ImplicitCastExpr {{.*}} 'const T' lvalue <NoOp>
+// CHECK-NEXT:             DeclRefExpr {{.*}} 'T' lvalue ParmVar {{.*}} 'self' 'T &'
+// CHECK-NEXT:     CXXOperatorCallExpr {{.*}} 'T' lvalue '--'
+// CHECK-NEXT:       ImplicitCastExpr {{.*}} 'T &(*)()' <FunctionToPointerDecay>
+// CHECK-NEXT:         DeclRefExpr {{.*}} 'T &()' lvalue CXXMethod {{.*}} 'operator--' 'T &()'
+// CHECK-NEXT:       DeclRefExpr {{.*}} 'T' lvalue ParmVar {{.*}} 'self' 'T &'
+// CHECK-NEXT:     ReturnStmt {{.*}} nrvo_candidate(Var {{.*}} 'tmp' 'T')
+// CHECK-NEXT:       CXXConstructExpr {{.*}} 'T' 'void (T &&) noexcept'
+// CHECK-NEXT:         ImplicitCastExpr {{.*}} 'T' xvalue <NoOp>
+// CHECK-NEXT:           DeclRefExpr {{.*}} 'T' lvalue Var {{.*}} 'tmp' 'T'
+
+struct U {
+  int v;
+  U &operator++();
+  U operator++(this U &self, int) = default;
+};
+void use_explicit_object(U u) { u++; }
+
+// CHECK:      CXXMethodDecl {{.*}} used constexpr operator++ 'U (U &, int) noexcept(false)' default implicit-inline
+// CHECK-NEXT:   ParmVarDecl {{.*}} used self this 'U &'
+// CHECK-NEXT:   ParmVarDecl {{.*}} 'int'
+// CHECK-NEXT:   CompoundStmt
+// CHECK-NEXT:     DeclStmt
+// CHECK-NEXT:       VarDecl {{.*}} used tmp 'U' nrvo callinit
+// CHECK-NEXT:         CXXConstructExpr {{.*}} 'U' 'void (const U &) noexcept'
+// CHECK-NEXT:           ImplicitCastExpr {{.*}} 'const U' lvalue <NoOp>
+// CHECK-NEXT:             DeclRefExpr {{.*}} 'U' lvalue ParmVar {{.*}} 'self' 'U &'
+// CHECK-NEXT:     CXXOperatorCallExpr {{.*}} 'U' lvalue '++'
+// CHECK-NEXT:       ImplicitCastExpr {{.*}} 'U &(*)()' <FunctionToPointerDecay>
+// CHECK-NEXT:         DeclRefExpr {{.*}} 'U &()' lvalue CXXMethod {{.*}} 'operator++' 'U &()'
+// CHECK-NEXT:       DeclRefExpr {{.*}} 'U' lvalue ParmVar {{.*}} 'self' 'U &'
+// CHECK-NEXT:     ReturnStmt {{.*}} nrvo_candidate(Var {{.*}} 'tmp' 'U')
+// CHECK-NEXT:       CXXConstructExpr {{.*}} 'U' 'void (U &&) noexcept'
+// CHECK-NEXT:         ImplicitCastExpr {{.*}} 'U' xvalue <NoOp>
+// CHECK-NEXT:           DeclRefExpr {{.*}} 'U' lvalue Var {{.*}} 'tmp' 'U'
+
+// A deleted defaulted postfix operator has no body.
+struct Deleted {
+  Deleted operator++(int) = default;
+};
+// CHECK:      CXXRecordDecl {{.*}} struct Deleted definition
+// CHECK:      CXXMethodDecl {{.*}} operator++ 'Deleted (int)' default_delete
+// CHECK-NEXT:   ParmVarDecl {{.*}} 'int'
+// CHECK-NOT:    CompoundStmt
diff --git a/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p1.cpp b/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p1.cpp
new file mode 100644
index 00000000000000..86a2620fbd7944
--- /dev/null
+++ b/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p1.cpp
@@ -0,0 +1,205 @@
+// RUN: %clang_cc1 -std=c++2d -verify %s
+
+// C++2d [over.inc.default]p1:
+//   A defaulted postfix increment or decrement operator function for a type C
+//   shall be a non-template function that
+//    -- has a first parameter of type "reference to C" or a first parameter of
+//       type "reference to volatile C", where the implicit object parameter
+//       (if any) is considered to be the first parameter,
+//    -- is defined as defaulted in C or in a context where C is complete, and
+//    -- has a declared return type of C.
+//   [...] A definition of a postfix increment or decrement operator as
+//   defaulted that appears in a class shall be the first declaration of that
+//   function.
+
+struct S {
+  S &operator++();
+  S &operator--();
+
+  S operator++(int) = default;
+  S operator--(int) = default;
+};
+
+// The implicit object parameter is the first parameter.
+struct Const {
+  Const &operator++();
+  Const operator++(int) const = default; // expected-error {{defaulted member postfix increment operator cannot be const-qualified}}
+};
+struct ConstDecrement {
+  ConstDecrement &operator--();
+  ConstDecrement operator--(int) const = default; // expected-error {{defaulted member postfix decrement operator cannot be const-qualified}}
+};
+struct Volatile {
+  Volatile &operator++() volatile;
+  Volatile operator++(int) volatile = default; // OK, "reference to volatile C"
+};
+struct RefQualified {
+  RefQualified &operator++();
+  RefQualified operator++(int) & = default;  // OK
+  RefQualified operator++(int) && = default; // OK, an rvalue reference is a "reference to C"
+};
+
+// Explicit object parameters.
+struct Explicit {
+  Explicit &operator++();
+  Explicit operator++(this Explicit &, int) = default; // OK
+};
+struct ExplicitVolatile {
+  ExplicitVolatile &operator++() volatile;
+  ExplicitVolatile operator++(this volatile ExplicitVolatile &, int) = default; // OK
+};
+struct ExplicitRvalue {
+  ExplicitRvalue &operator++();
+  ExplicitRvalue operator++(this ExplicitRvalue &&, int) = default; // OK
+};
+struct ExplicitConst {
+  ExplicitConst &operator++();
+  ExplicitConst operator++(this const ExplicitConst &, int) = default; // expected-error {{invalid explicit object parameter type for defaulted postfix increment operator; found 'const ExplicitConst &', expected 'ExplicitConst &' or 'volatile ExplicitConst &'}}
+};
+struct ExplicitOther {
+  ExplicitOther &operator++();
+  ExplicitOther operator++(this S &, int) = default; // expected-error {{invalid explicit object parameter type for defaulted postfix increment operator; found 'S &', expected 'ExplicitOther &' or 'volatile ExplicitOther &'}}
+};
+struct ExplicitByValue {
+  ExplicitByValue &operator--();
+  ExplicitByValue operator--(this ExplicitByValue, int) = default; // expected-error {{invalid explicit object parameter type for defaulted postfix decrement operator; found 'ExplicitByValue', expected 'ExplicitByValue &' or 'volatile ExplicitByValue &'}}
+};
+
+// Return type.
+struct ReturnsRef {
+  ReturnsRef &operator++();
+  ReturnsRef &operator++(int) = default; // expected-error {{return type for defaulted postfix increment operator must be 'ReturnsRef', not 'ReturnsRef &'}}
+};
+struct ReturnsConst {
+  ReturnsConst &operator++();
+  const ReturnsConst operator++(int) = default; // expected-error {{return type for defaulted postfix increment operator must be 'ReturnsConst', not 'const ReturnsConst'}}
+};
+struct ReturnsAuto {
+  ReturnsAuto &operator++();
+  auto operator++(int) = default; // expected-error {{return type for defaulted postfix increment operator must be 'ReturnsAuto', not 'auto'}}
+};
+struct ReturnsDecltypeAuto {
+  ReturnsDecltypeAuto &operator--();
+  decltype(auto) operator--(int) = default; // expected-error {{return type for defaulted postfix decrement operator must be 'ReturnsDecltypeAuto', not 'decltype(auto)'}}
+};
+struct ReturnsInt {
+  ReturnsInt &operator++();
+  int operator++(int) = default; // expected-error {{return type for defaulted postfix increment operator must be 'ReturnsInt', not 'int'}}
+};
+struct ReturnsAlias {
+  using Self = ReturnsAlias;
+  Self &operator++();
+  Self operator++(int) = default; // OK
+};
+
+// Non-member functions.
+struct N {
+  N &operator++();
+  N &operator--();
+};
+N operator++(N &, int) = default;  // OK
+N operator--(N &&, int) = default; // OK, an rvalue reference is a "reference to C"
+
+struct NV {
+  NV &operator++() volatile;
+  NV(const volatile NV &);
+  NV(NV &&);
+};
+NV operator++(volatile NV &, int) = default; // OK
+
+struct NConst { NConst &operator++(); };
+NConst operator++(const NConst &, int) = default; // expected-error {{invalid first parameter type for defaulted postfix increment operator; found 'const NConst &', expected 'NConst &' or 'volatile NConst &'}}
+struct NValue { NValue &operator++(); };
+NValue operator++(NValue, int) = default; // expected-error {{invalid first parameter type for defaulted postfix increment operator; found 'NValue', expected 'NValue &' or 'volatile NValue &'}}
+struct NRet { NRet &operator--(); };
+int operator--(NRet &, int) = default; // expected-error {{return type for defaulted postfix decrement operator must be 'NRet', not 'int'}}
+struct NMismatch { NMismatch &operator++(); };
+S operator++(NMismatch &, int) = default; // expected-error {{return type for defaulted postfix increment operator must be 'NMismatch', not 'S'}}
+struct NUnknown { NUnknown &operator++(); };
+S &operator++(NUnknown, int) = default; // expected-error {{invalid first parameter type for defaulted non-member postfix increment operator; found 'NUnknown', expected reference to a non-const class or enumeration type}}
+
+// Enumerations.
+enum E { e };
+E &operator++(E &);
+E operator++(E &, int) = default; // OK
+enum class EC { ec };
+EC operator++(EC &, int) = default; // OK, defined as deleted (see p2.cpp)
+// expected-warning at -1 {{explicitly defaulted postfix increment operator is implicitly deleted}}
+// expected-note at -2 {{defaulted 'operator++' is implicitly deleted because there is no viable prefix 'operator++' for an lvalue of type 'EC'}}
+// expected-note at -3 {{replace 'default' with 'delete'}}
+
+// C must be complete.
+struct Incomplete; // expected-note {{forward declaration of 'Incomplete'}}
+Incomplete operator++(Incomplete &, int) = default; // expected-error {{incomplete result type 'Incomplete' in function definition}}
+struct IncompleteFriend; // expected-note {{forward declaration of 'IncompleteFriend'}}
+struct Befriender {
+  friend IncompleteFriend operator++(IncompleteFriend &, int) = default; // expected-error {{cannot default postfix increment operator for incomplete type 'IncompleteFriend'}}
+};
+struct DefinedLater {
+  friend DefinedLater operator++(DefinedLater &, int) = default; // OK, defaulted in C
+  DefinedLater &operator++();
+};
+
+// A definition as defaulted that appears in a class shall be the first
+// declaration of that function.
+struct First;
+First operator++(First &, int); // expected-note {{previous declaration is here}}
+struct First {
+  First &operator++();
+  friend First operator++(First &, int) = default; // expected-error {{defaulting this postfix increment operator is not allowed because it was already declared outside the class}}
+};
+struct OutOfLine {
+  OutOfLine &operator++();
+  OutOfLine operator++(int);
+};
+OutOfLine OutOfLine::operator++(int) = default; // OK
+struct OutOfLineFriend {
+  OutOfLineFriend &operator--();
+  friend OutOfLineFriend operator--(OutOfLineFriend &, int);
+};
+OutOfLineFriend operator--(OutOfLineFriend &, int) = default; // OK
+struct Redefined {
+  Redefined &operator++();
+  Redefined operator++(int) = default;
+};
+Redefined Redefined::operator++(int) { return *this; } // expected-error {{definition of explicitly defaulted function}}
+
+// Templates cannot be defaulted.
+struct Template {
+  Template &operator++();
+  template <typename T = void> Template operator++(int) = default; // expected-error {{postfix increment operator template cannot be defaulted}}
+};
+template <typename T = void> S operator--(S &, int) = default; // expected-error {{postfix decrement operator template cannot be defaulted}}
+
+// Only the postfix forms can be defaulted.
+struct Prefix {
+  Prefix &operator++() = default; // expected-error {{only the postfix form of 'operator++' can be defaulted}}
+  Prefix &operator--() = default; // expected-error {{only the postfix form of 'operator--' can be defaulted}}
+};
+struct PrefixNonMember {};
+PrefixNonMember &operator++(PrefixNonMember &) = default; // expected-error {{only the postfix form of 'operator++' can be defaulted}}
+
+// Other operators still cannot be defaulted.
+struct Other {
+  Other operator+(int) = default; // expected-error {{only special member functions, comparison operators, and postfix increment and decrement operators may be defaulted}}
+};
+
+// Dependent declarations are checked when instantiated.
+template <typename T> struct DependentReturn {
+  DependentReturn &operator++();
+  T operator++(int) = default; // expected-error {{return type for defaulted postfix increment operator must be 'DependentReturn<int>', not 'int'}}
+};
+DependentReturn<int> dr1; // expected-note {{in instantiation of template class 'DependentReturn<int>' requested here}}
+
+template <typename T> struct DependentObject {
+  DependentObject &operator++();
+  DependentObject operator++(this T &, int) = default; // expected-error {{invalid explicit object parameter type for defaulted postfix increment operator; found 'int &', expected 'DependentObject<int> &' or 'volatile DependentObject<int> &'}}
+};
+DependentObject<int> do1; // expected-note {{in instantiation of template class 'DependentObject<int>' requested here}}
+
+template <typename T> struct DependentFriend {
+  DependentFriend &operator++();
+  friend DependentFriend operator++(DependentFriend &, int) = default; // OK
+  friend int operator--(T &, int) = default;                           // expected-error {{return type for defaulted postfix decrement operator must be 'S', not 'int'}}
+};
+DependentFriend<S> df1; // expected-note {{in instantiation of template class 'DependentFriend<S>' requested here}}
diff --git a/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p2.cpp b/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p2.cpp
new file mode 100644
index 00000000000000..b7794fc8d064ff
--- /dev/null
+++ b/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p2.cpp
@@ -0,0 +1,192 @@
+// RUN: %clang_cc1 -std=c++2d -verify %s
+
+// C++2d [over.inc.default]p2:
+//   A defaulted postfix increment or decrement operator function for a type C
+//   is defined as deleted if
+//    -- C is a class type for which overload resolution, as applied to
+//       direct-initialization of a variable of type C from an lvalue of type
+//       C, does not result in a usable candidate,
+//    -- C has a destructor that is deleted or inaccessible from the context of
+//       the function-body, or
+//    -- for an lvalue c of type C, overload resolution as applied to ++c for a
+//       postfix increment operator function or --c for a postfix decrement
+//       operator function does not result in a usable candidate.
+
+struct NoPrefix {
+  NoPrefix operator++(int) = default; // #NoPrefix
+  // expected-warning@#NoPrefix {{explicitly defaulted postfix increment operator is implicitly deleted}}
+  // expected-note@#NoPrefix 2 {{defaulted 'operator++' is implicitly deleted because there is no viable prefix 'operator++' for an lvalue of type 'NoPrefix'}}
+  // expected-note@#NoPrefix {{replace 'default' with 'delete'}}
+  // expected-note@#NoPrefix {{explicitly defaulted function was implicitly deleted here}}
+};
+void use_no_prefix(NoPrefix n) {
+  n++; // expected-error {{object of type 'NoPrefix' cannot be incremented because its defaulted postfix increment operator is implicitly deleted}}
+}
+
+struct NoPrefixDecrement {
+  NoPrefixDecrement &operator++();
+  NoPrefixDecrement operator--(int) = default; // #NoPrefixDecrement
+  // expected-warning@#NoPrefixDecrement {{explicitly defaulted postfix decrement operator is implicitly deleted}}
+  // expected-note@#NoPrefixDecrement 2 {{defaulted 'operator--' is implicitly deleted because there is no viable prefix 'operator--' for an lvalue of type 'NoPrefixDecrement'}}
+  // expected-note@#NoPrefixDecrement {{replace 'default' with 'delete'}}
+  // expected-note@#NoPrefixDecrement {{explicitly defaulted function was implicitly deleted here}}
+};
+void use_no_prefix_decrement(NoPrefixDecrement n) {
+  n--; // expected-error {{object of type 'NoPrefixDecrement' cannot be decremented because its defaulted postfix decrement operator is implicitly deleted}}
+}
+
+// There is no built-in increment operator for enumeration types.
+enum class E {};
+E operator++(E &, int) = default; // #E
+// expected-warning@#E {{explicitly defaulted postfix increment operator is implicitly deleted}}
+// expected-note@#E 2 {{defaulted 'operator++' is implicitly deleted because there is no viable prefix 'operator++' for an lvalue of type 'E'}}
+// expected-note@#E {{replace 'default' with 'delete'}}
+// expected-note@#E {{explicitly defaulted function was implicitly deleted here}}
+void use_e(E e) {
+  e++; // expected-error {{object of type 'E' cannot be incremented because its defaulted postfix increment operator is implicitly deleted}}
+}
+
+// A conversion to a built-in type provides a usable candidate.
+struct ConvertsToInt {
+  operator int &();
+  ConvertsToInt operator++(int) = default; // OK
+};
+struct AmbiguousBuiltin {
+  operator int &();
+  operator long &();
+  AmbiguousBuiltin operator++(int) = default; // #AmbiguousBuiltin
+  // expected-warning@#AmbiguousBuiltin {{explicitly defaulted postfix increment operator is implicitly deleted}}
+  // expected-note@#AmbiguousBuiltin {{defaulted 'operator++' is implicitly deleted because prefix 'operator++' for an lvalue of type 'AmbiguousBuiltin' is ambiguous}}
+  // expected-note@#AmbiguousBuiltin {{built-in candidate operator++(int &)}}
+  // expected-note@#AmbiguousBuiltin {{built-in candidate operator++(long &)}}
+  // expected-note@#AmbiguousBuiltin {{replace 'default' with 'delete'}}
+};
+
+struct DeletedPrefix {
+  DeletedPrefix &operator++() = delete; // expected-note {{'operator++' has been explicitly marked deleted here}}
+  DeletedPrefix operator++(int) = default; // #DeletedPrefix
+  // expected-warning@#DeletedPrefix {{explicitly defaulted postfix increment operator is implicitly deleted}}
+  // expected-note@#DeletedPrefix {{defaulted 'operator++' is implicitly deleted because it would invoke a deleted prefix 'operator++'}}
+  // expected-note@#DeletedPrefix {{replace 'default' with 'delete'}}
+};
+
+struct RvaluePrefix {
+  RvaluePrefix &operator++() &&;
+  RvaluePrefix operator++(int) = default; // #RvaluePrefix
+  // expected-warning@#RvaluePrefix {{explicitly defaulted postfix increment operator is implicitly deleted}}
+  // expected-note@#RvaluePrefix {{defaulted 'operator++' is implicitly deleted because there is no viable prefix 'operator++' for an lvalue of type 'RvaluePrefix'}}
+  // expected-note@#RvaluePrefix {{replace 'default' with 'delete'}}
+};
+
+class PrivatePrefix {
+  PrivatePrefix &operator++(); // expected-note {{declared private here}}
+
+public:
+  PrivatePrefix operator--(int) = default; // #PrivatePrefixDecrement
+  // expected-warning@#PrivatePrefixDecrement {{explicitly defaulted postfix decrement operator is implicitly deleted}}
+  // expected-note@#PrivatePrefixDecrement {{defaulted 'operator--' is implicitly deleted because there is no viable prefix 'operator--' for an lvalue of type 'PrivatePrefix'}}
+  // expected-note@#PrivatePrefixDecrement {{replace 'default' with 'delete'}}
+};
+PrivatePrefix operator++(PrivatePrefix &, int) = default; // #PrivatePrefix
+// expected-warning@#PrivatePrefix {{explicitly defaulted postfix increment operator is implicitly deleted}}
+// expected-note@#PrivatePrefix {{defaulted 'operator++' is implicitly deleted because it would invoke a private prefix 'operator++' of 'PrivatePrefix'}}
+// expected-note@#PrivatePrefix {{replace 'default' with 'delete'}}
+struct FriendOfPrivatePrefix {
+  // A friend has access to the prefix operator.
+  friend PrivatePrefix operator--(PrivatePrefix &, int) = default; // #FriendOfPrivatePrefix
+  // expected-warning@#FriendOfPrivatePrefix {{explicitly defaulted postfix decrement operator is implicitly deleted}}
+  // expected-note@#FriendOfPrivatePrefix {{defaulted 'operator--' is implicitly deleted because there is no viable prefix 'operator--' for an lvalue of type 'PrivatePrefix'}}
+  // expected-note@#FriendOfPrivatePrefix {{replace 'default' with 'delete'}}
+};
+
+struct DeletedCopy {
+  DeletedCopy &operator++();
+  DeletedCopy(const DeletedCopy &) = delete; // expected-note {{'DeletedCopy' has been explicitly marked deleted here}}
+  DeletedCopy operator++(int) = default; // #DeletedCopy
+  // expected-warning@#DeletedCopy {{explicitly defaulted postfix increment operator is implicitly deleted}}
+  // expected-note@#DeletedCopy {{defaulted 'operator++' is implicitly deleted because it would invoke a deleted constructor}}
+  // expected-note@#DeletedCopy {{replace 'default' with 'delete'}}
+};
+
+struct MoveOnly {
+  MoveOnly &operator++();
+  MoveOnly(MoveOnly &&); // expected-note {{copy constructor is implicitly deleted because 'MoveOnly' has a user-declared move constructor}}
+  MoveOnly operator++(int) = default; // #MoveOnly
+  // expected-warning@#MoveOnly {{explicitly defaulted postfix increment operator is implicitly deleted}}
+  // expected-note@#MoveOnly {{defaulted 'operator++' is implicitly deleted because it would invoke a deleted constructor}}
+  // expected-note@#MoveOnly {{replace 'default' with 'delete'}}
+};
+
+struct AmbiguousCopy {
+  AmbiguousCopy &operator++();
+  AmbiguousCopy(AmbiguousCopy &, int = 0);  // expected-note {{candidate constructor}}
+  AmbiguousCopy(AmbiguousCopy &, long = 0); // expected-note {{candidate constructor}}
+  AmbiguousCopy operator++(int) = default; // #AmbiguousCopy
+  // expected-warning@#AmbiguousCopy {{explicitly defaulted postfix increment operator is implicitly deleted}}
+  // expected-note@#AmbiguousCopy {{defaulted 'operator++' is implicitly deleted because copy construction of an lvalue of type 'AmbiguousCopy' is ambiguous}}
+  // expected-note@#AmbiguousCopy {{replace 'default' with 'delete'}}
+};
+
+class PrivateCopy {
+  PrivateCopy(const PrivateCopy &); // expected-note {{declared private here}}
+
+public:
+  PrivateCopy &operator++();
+  PrivateCopy &operator--();
+  PrivateCopy operator++(int) = default; // OK, a member has access to the copy constructor
+};
+PrivateCopy operator--(PrivateCopy &, int) = default; // #PrivateCopy
+// expected-warning@#PrivateCopy {{explicitly defaulted postfix decrement operator is implicitly deleted}}
+// expected-note@#PrivateCopy {{defaulted 'operator--' is implicitly deleted because it would invoke a private constructor of 'PrivateCopy'}}
+// expected-note@#PrivateCopy {{replace 'default' with 'delete'}}
+
+struct DeletedDestructor {
+  DeletedDestructor &operator++();
+  ~DeletedDestructor() = delete; // expected-note {{'~DeletedDestructor' has been explicitly marked deleted here}}
+  DeletedDestructor operator++(int) = default; // #DeletedDestructor
+  // expected-warning@#DeletedDestructor {{explicitly defaulted postfix increment operator is implicitly deleted}}
+  // expected-note@#DeletedDestructor {{defaulted 'operator++' is implicitly deleted because it would invoke a deleted destructor}}
+  // expected-note@#DeletedDestructor {{replace 'default' with 'delete'}}
+};
+
+class PrivateDestructor {
+  ~PrivateDestructor(); // expected-note {{declared private here}}
+
+public:
+  PrivateDestructor &operator++();
+  PrivateDestructor(const PrivateDestructor &);
+  PrivateDestructor operator++(int) = default; // OK, a member has access to the destructor
+};
+PrivateDestructor operator++(PrivateDestructor &, int) = default; // #PrivateDestructor
+// expected-warning@#PrivateDestructor {{explicitly defaulted postfix increment operator is implicitly deleted}}
+// expected-note@#PrivateDestructor {{defaulted 'operator++' is implicitly deleted because it would invoke a private destructor of 'PrivateDestructor'}}
+// expected-note@#PrivateDestructor {{replace 'default' with 'delete'}}
+
+// A function that is not defaulted on its first declaration cannot be deleted.
+struct NotFirst {
+  NotFirst &operator++();
+  NotFirst(const NotFirst &) = delete; // expected-note {{'NotFirst' has been explicitly marked deleted here}}
+  NotFirst operator++(int);
+};
+NotFirst NotFirst::operator++(int) = default; // expected-error {{defaulting this postfix increment operator would delete it after its first declaration}}
+// expected-note at -1 {{defaulted 'operator++' is implicitly deleted because it would invoke a deleted constructor}}
+
+// No warning is produced for a template instantiation, but the function is
+// still deleted.
+template <typename T> struct Wrapper {
+  T v;
+  Wrapper operator++(int) = default; // #Wrapper
+  // expected-note@#Wrapper {{explicitly defaulted function was implicitly deleted here}}
+  // expected-note@#Wrapper {{defaulted 'operator++' is implicitly deleted because there is no viable prefix 'operator++' for an lvalue of type 'Wrapper<int>'}}
+};
+void use_wrapper(Wrapper<int> w) {
+  w++; // expected-error {{object of type 'Wrapper<int>' cannot be incremented because its defaulted postfix increment operator is implicitly deleted}}
+}
+
+// -Wdefaulted-function-deleted can be disabled.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdefaulted-function-deleted"
+struct Quiet {
+  Quiet operator++(int) = default;
+};
+#pragma clang diagnostic pop
diff --git a/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p3.cpp b/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p3.cpp
new file mode 100644
index 00000000000000..2f8d1d9788c672
--- /dev/null
+++ b/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p3.cpp
@@ -0,0 +1,263 @@
+// RUN: %clang_cc1 -std=c++2d -verify %s
+
+// C++2d [over.inc.default]p3:
+//   The implicit definition of a defaulted postfix increment or decrement
+//   operator function F that is not defined as deleted for a type C is
+//   equivalent to:
+//     C tmp(c);
+//     ++c;
+//     return tmp;
+//   for a postfix increment operator function, or
+//     C tmp(c);
+//     --c;
+//     return tmp;
+//   for a postfix decrement operator function, where tmp is a variable defined
+//   for exposition only, and c is an lvalue that denotes *this if F is an
+//   implicit object member function, or the first parameter of F otherwise.
+
+struct Counter {
+  int v;
+  constexpr Counter &operator++() { ++v; return *this; }
+  constexpr Counter &operator--() { --v; return *this; }
+  Counter operator++(int) = default;
+  Counter operator--(int) = default;
+};
+constexpr int counter() {
+  Counter c{1};
+  Counter a = c++;
+  Counter b = c--;
+  return a.v * 100 + b.v * 10 + c.v;
+}
+static_assert(counter() == 121);
+
+struct NonMember {
+  int v;
+  constexpr NonMember &operator++() { ++v; return *this; }
+};
+constexpr NonMember operator++(NonMember &, int) = default;
+constexpr int non_member() {
+  NonMember n{1};
+  NonMember a = n++;
+  return a.v * 10 + n.v;
+}
+static_assert(non_member() == 12);
+
+struct ExplicitObject {
+  int v;
+  constexpr ExplicitObject &operator++() { ++v; return *this; }
+  ExplicitObject operator++(this ExplicitObject &self, int) = default;
+};
+constexpr int explicit_object() {
+  ExplicitObject e{1};
+  ExplicitObject a = e++;
+  return a.v * 10 + e.v;
+}
+static_assert(explicit_object() == 12);
+
+struct RvalueObject {
+  int v;
+  constexpr RvalueObject &operator++() { ++v; return *this; }
+  RvalueObject operator++(int) && = default;
+};
+constexpr int rvalue_object() {
+  RvalueObject r{1};
+  RvalueObject a = static_cast<RvalueObject &&>(r)++;
+  return a.v * 10 + r.v;
+}
+static_assert(rvalue_object() == 12);
+
+// The result of the prefix operator is discarded, so its type is irrelevant.
+struct ReturnsVoid {
+  int v;
+  constexpr void operator++() { ++v; }
+  ReturnsVoid operator++(int) = default;
+};
+constexpr int returns_void() {
+  ReturnsVoid r{1};
+  ReturnsVoid a = r++;
+  return a.v * 10 + r.v;
+}
+static_assert(returns_void() == 12);
+
+// Enumerations.
+enum E { e0, e1, e2 };
+constexpr E &operator++(E &e) { return e = E(e + 1); }
+constexpr E operator++(E &, int) = default;
+constexpr int enumeration() {
+  E e = e0;
+  E a = e++;
+  return a * 10 + e;
+}
+static_assert(enumeration() == 1);
+
+// The prefix operator may be a built-in operator reached through a conversion
+// function.
+struct ConvertsToInt {
+  int v;
+  constexpr operator int &() { return v; }
+  ConvertsToInt operator++(int) = default;
+};
+constexpr int converts_to_int() {
+  ConvertsToInt c{1};
+  ConvertsToInt a = c++;
+  return a.v * 10 + c.v;
+}
+static_assert(converts_to_int() == 12);
+
+// Class templates.
+template <typename T> struct Wrapper {
+  T v;
+  constexpr Wrapper &operator++() { ++v; return *this; }
+  constexpr Wrapper &operator--() { --v; return *this; }
+  Wrapper operator++(int) = default;
+  friend Wrapper operator--(Wrapper &, int) = default;
+};
+constexpr int wrapper() {
+  Wrapper<int> w{5};
+  Wrapper<int> a = w++;
+  Wrapper<int> b = w--;
+  return a.v * 100 + b.v * 10 + w.v;
+}
+static_assert(wrapper() == 565);
+
+// Direct-initialization of the copy permits an explicit copy constructor, but
+// the return statement needs a usable move or copy constructor.
+struct ExplicitCopy {
+  explicit ExplicitCopy(const ExplicitCopy &);
+  ExplicitCopy(ExplicitCopy &&);
+  ExplicitCopy &operator++();
+  ExplicitCopy operator++(int) = default; // OK
+};
+void explicit_copy(ExplicitCopy e) { e++; }
+
+struct ExplicitCopyOnly {
+  explicit ExplicitCopyOnly(const ExplicitCopyOnly &); // expected-note {{explicit constructor is not a candidate}}
+  ExplicitCopyOnly &operator++();
+  ExplicitCopyOnly operator++(int) = default; // expected-error {{no matching constructor for initialization of 'ExplicitCopyOnly'}}
+};
+void explicit_copy_only(ExplicitCopyOnly e) {
+  e++; // expected-note {{in defaulted postfix increment operator for 'ExplicitCopyOnly' first required here}}
+}
+
+struct DeletedMove {
+  DeletedMove(const DeletedMove &);
+  DeletedMove(DeletedMove &&) = delete; // expected-note {{'DeletedMove' has been explicitly marked deleted here}}
+  DeletedMove &operator++();
+  DeletedMove operator++(int) = default; // expected-error {{call to deleted constructor of 'DeletedMove'}}
+};
+void deleted_move(DeletedMove d) {
+  d++; // expected-note {{in defaulted postfix increment operator for 'DeletedMove' first required here}}
+}
+
+// Attributes on the prefix operator are honored, as for any other call.
+struct Nodiscard {
+  [[nodiscard]] Nodiscard &operator++();
+  Nodiscard operator++(int) = default; // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+};
+void nodiscard(Nodiscard n) {
+  n++; // expected-note {{in defaulted postfix increment operator for 'Nodiscard' first required here}}
+}
+
+// Volatile objects.
+struct Volatile {
+  Volatile();
+  Volatile(const Volatile &);
+  Volatile(const volatile Volatile &);
+  Volatile(Volatile &&);
+  Volatile &operator++() volatile;
+  Volatile operator++(int) volatile = default;
+};
+void use_volatile() {
+  volatile Volatile v;
+  v++;
+}
+
+// Name lookup for the prefix operator is performed from a context equivalent
+// to the function body, even if the body is synthesized elsewhere.
+namespace Lookup {
+namespace A {
+struct X { int v; };
+struct Y;
+} // namespace A
+namespace B {
+constexpr A::Y &operator++(A::Y &y);
+} // namespace B
+namespace C {
+namespace Nested {
+constexpr A::X &operator++(A::X &x) { ++x.v; return x; }
+} // namespace Nested
+using namespace Nested;
+// Found by unqualified lookup through the using-directive, not by ADL.
+constexpr A::X operator++(A::X &, int) = default;
+} // namespace C
+constexpr int non_member() {
+  A::X x{1};
+  A::X a = C::operator++(x, 0);
+  return a.v * 10 + x.v;
+}
+static_assert(non_member() == 12);
+
+namespace A {
+using namespace B;
+struct Y {
+  int v;
+  Y operator++(int) = default;
+};
+} // namespace A
+constexpr A::Y &B::operator++(A::Y &y) { ++y.v; return y; }
+namespace D {
+constexpr int member() {
+  A::Y y{1};
+  A::Y a = y++; // The body is synthesized here, where B::operator++ is not visible.
+  return a.v * 10 + y.v;
+}
+static_assert(member() == 12);
+} // namespace D
+} // namespace Lookup
+
+// C++2d [except.spec]p10:
+//   The exception specification for [...] a postfix increment or decrement
+//   operator function without a noexcept-specifier that is defaulted on its
+//   first declaration is potentially-throwing if and only if any expression in
+//   the implicit definition is potentially-throwing.
+struct Noexcept {
+  Noexcept &operator++() noexcept;
+  Noexcept operator++(int) = default;
+};
+static_assert(noexcept(Noexcept{}++));
+struct ThrowingPrefix {
+  ThrowingPrefix &operator++();
+  ThrowingPrefix operator++(int) = default;
+};
+static_assert(!noexcept(ThrowingPrefix{}++));
+struct ThrowingCopy {
+  ThrowingCopy();
+  ThrowingCopy(const ThrowingCopy &) noexcept(false);
+  ThrowingCopy &operator++() noexcept;
+  ThrowingCopy operator++(int) = default;
+};
+static_assert(!noexcept(ThrowingCopy{}++));
+struct ThrowingDestructor {
+  ThrowingDestructor();
+  ~ThrowingDestructor() noexcept(false);
+  ThrowingDestructor &operator++() noexcept;
+  ThrowingDestructor operator++(int) = default;
+};
+static_assert(!noexcept(ThrowingDestructor{}++));
+struct ExplicitNoexcept {
+  ExplicitNoexcept &operator++();
+  ExplicitNoexcept operator++(int) noexcept = default;
+};
+static_assert(noexcept(ExplicitNoexcept{}++));
+struct ThrowingNonMember {
+  ThrowingNonMember &operator++();
+};
+ThrowingNonMember operator++(ThrowingNonMember &, int) = default;
+extern ThrowingNonMember throwing_non_member;
+static_assert(!noexcept(throwing_non_member++));
+struct NoexceptNonMember {
+  NoexceptNonMember &operator++() noexcept;
+};
+NoexceptNonMember operator++(NoexceptNonMember &, int) = default;
+extern NoexceptNonMember noexcept_non_member;
+static_assert(noexcept(noexcept_non_member++));
diff --git a/clang/test/CodeGenCXX/cxx2d-defaulted-postfix-operators.cpp b/clang/test/CodeGenCXX/cxx2d-defaulted-postfix-operators.cpp
new file mode 100644
index 00000000000000..e84f07710802a4
--- /dev/null
+++ b/clang/test/CodeGenCXX/cxx2d-defaulted-postfix-operators.cpp
@@ -0,0 +1,53 @@
+// RUN: %clang_cc1 -std=c++2d -triple x86_64-linux-gnu -emit-llvm -o - %s | FileCheck %s
+
+// A defaulted postfix increment or decrement operator copies the object,
+// applies the prefix operator, and returns the copy (with NRVO).
+
+struct Trivial {
+  int v;
+  Trivial &operator++();
+  Trivial operator++(int) = default;
+};
+Trivial use_trivial(Trivial t) { return t++; }
+
+// CHECK-LABEL: define {{.*}} @_Z11use_trivial7Trivial(
+// CHECK:         call i32 @_ZN7TrivialppEi(ptr {{.*}} %t, i32 noundef 0)
+
+// A function defaulted on its first declaration is implicitly inline.
+// CHECK-LABEL: define linkonce_odr i32 @_ZN7TrivialppEi(ptr {{.*}} %this, i32 noundef %0)
+// CHECK:         call void @llvm.memcpy.p0.p0.i64(ptr align 4 %retval, ptr align 4 %this1, i64 4, i1 false)
+// CHECK-NEXT:    call {{.*}} @_ZN7TrivialppEv(ptr {{.*}} %this1)
+// CHECK:         ret i32
+
+struct NonTrivial {
+  int *p;
+  NonTrivial(const NonTrivial &);
+  NonTrivial(NonTrivial &&);
+  ~NonTrivial();
+  NonTrivial &operator--();
+};
+NonTrivial operator--(NonTrivial &, int) = default;
+NonTrivial use_non_trivial(NonTrivial n) { return n--; }
+
+// CHECK-LABEL: define {{.*}} @_Z15use_non_trivial10NonTrivial(
+// CHECK:         call void @_ZmmR10NonTriviali(ptr {{.*}} %agg.result, ptr {{.*}} %n, i32 noundef 0)
+
+// The copy is constructed directly in the return slot; no move constructor is
+// called.
+// CHECK-LABEL: define linkonce_odr void @_ZmmR10NonTriviali(ptr {{.*}} %agg.result, ptr {{.*}} %0, i32 noundef %1)
+// CHECK:         call void @_ZN10NonTrivialC1ERKS_(ptr {{.*}} %agg.result, ptr {{.*}})
+// CHECK:         call {{.*}} @_ZN10NonTrivialmmEv(
+// CHECK-NOT:     call void @_ZN10NonTrivialC1EOS_
+// CHECK:         ret void
+
+struct Explicit {
+  int v;
+  Explicit &operator++();
+  Explicit operator++(this Explicit &self, int) = default;
+};
+Explicit use_explicit(Explicit e) { return e++; }
+
+// CHECK-LABEL: define linkonce_odr i32 @_ZNH8ExplicitppERS_i(ptr {{.*}} %self, i32 noundef %0)
+// CHECK:         call void @llvm.memcpy.p0.p0.i64(ptr align 4 %retval, ptr align 4 %{{.*}}, i64 4, i1 false)
+// CHECK:         call {{.*}} @_ZN8ExplicitppEv(
+// CHECK:         ret i32
diff --git a/clang/test/PCH/cxx2d-defaulted-postfix-operators.cpp b/clang/test/PCH/cxx2d-defaulted-postfix-operators.cpp
new file mode 100644
index 00000000000000..9d4c22cd00ee69
--- /dev/null
+++ b/clang/test/PCH/cxx2d-defaulted-postfix-operators.cpp
@@ -0,0 +1,59 @@
+// RUN: %clang_cc1 -std=c++2d -verify -include %s %s
+//
+// RUN: %clang_cc1 -std=c++2d -emit-pch %s -o %t.pch
+// RUN: %clang_cc1 -std=c++2d -include-pch %t.pch %s -verify
+//
+// RUN: %clang_cc1 -std=c++2d -emit-pch -fpch-instantiate-templates %s -o %t.pch
+// RUN: %clang_cc1 -std=c++2d -include-pch %t.pch %s -verify
+
+// expected-no-diagnostics
+
+#ifndef INCLUDED
+#define INCLUDED
+
+struct Counter {
+  int v;
+  constexpr Counter &operator++() { ++v; return *this; }
+  constexpr Counter &operator--() { --v; return *this; }
+  Counter operator++(int) = default;
+  friend Counter operator--(Counter &, int) = default;
+};
+
+// Ensure that we can round-trip DefaultedOrDeletedInfo through an AST file.
+namespace LookupContext {
+namespace A {
+struct X { int v; };
+}
+namespace B {
+constexpr A::X &operator++(A::X &x) { ++x.v; return x; }
+}
+namespace A {
+using namespace B;
+template <typename T> struct Y {
+  T x;
+  constexpr Y &operator++() { ++x; return *this; }
+  Y operator++(int) = default;
+};
+} // namespace A
+} // namespace LookupContext
+
+#else
+
+constexpr int counter() {
+  Counter c{1};
+  Counter a = c++;
+  Counter b = c--;
+  return a.v * 100 + b.v * 10 + c.v;
+}
+static_assert(counter() == 121);
+
+namespace Other {
+constexpr int lookup() {
+  LookupContext::A::Y<LookupContext::A::X> y{{1}};
+  LookupContext::A::Y<LookupContext::A::X> a = y++;
+  return a.x.v * 10 + y.x.v;
+}
+static_assert(lookup() == 12);
+} // namespace Other
+
+#endif
diff --git a/clang/test/SemaCXX/cxx2d-defaulted-postfix-operators.cpp b/clang/test/SemaCXX/cxx2d-defaulted-postfix-operators.cpp
new file mode 100644
index 00000000000000..da75d03dac96d9
--- /dev/null
+++ b/clang/test/SemaCXX/cxx2d-defaulted-postfix-operators.cpp
@@ -0,0 +1,108 @@
+// RUN: %clang_cc1 -std=c++03 -verify=expected,cxx03,pre14,pre20,pre23,pre2d %s
+// RUN: %clang_cc1 -std=c++11 -verify=expected,pre14,pre20,pre23,pre2d %s
+// RUN: %clang_cc1 -std=c++14 -verify=expected,pre20,pre23,pre2d %s
+// RUN: %clang_cc1 -std=c++17 -verify=expected,pre20,pre23,pre2d %s
+// RUN: %clang_cc1 -std=c++20 -verify=expected,cxx20,pre23,pre2d %s
+// RUN: %clang_cc1 -std=c++23 -verify=expected,cxx20,pre2d %s
+// RUN: %clang_cc1 -std=c++26 -verify=expected,cxx20,pre2d %s
+// RUN: %clang_cc1 -std=c++2d -verify=expected,cxx2d %s
+// RUN: %clang_cc1 -std=c++2d -Wpre-c++2d-compat -verify=expected,cxx2d,compat %s
+
+// Defaulted postfix increment and decrement operators (P3668R4) are a C++2d
+// feature that is available in all language modes as an extension.
+
+struct S {
+  int v;
+  S &operator++();
+  S &operator--();
+  S operator++(int) = default;
+  // pre2d-warning at -1 {{defaulted postfix increment and decrement operators are a C++2d extension}}
+  // compat-warning at -2 {{defaulted postfix increment and decrement operators are incompatible with C++ standards before C++2d}}
+  // cxx03-warning at -3 {{defaulted function definitions are a C++11 extension}}
+  S operator--(int) = default;
+  // pre2d-warning at -1 {{defaulted postfix increment and decrement operators are a C++2d extension}}
+  // compat-warning at -2 {{defaulted postfix increment and decrement operators are incompatible with C++ standards before C++2d}}
+  // cxx03-warning at -3 {{defaulted function definitions are a C++11 extension}}
+};
+void use_s(S s) {
+  s++;
+  s--;
+}
+
+struct N {
+  N &operator++();
+};
+N operator++(N &, int) = default;
+// pre2d-warning at -1 {{defaulted postfix increment and decrement operators are a C++2d extension}}
+// compat-warning at -2 {{defaulted postfix increment and decrement operators are incompatible with C++ standards before C++2d}}
+// cxx03-warning at -3 {{defaulted function definitions are a C++11 extension}}
+void use_n(N n) { n++; }
+
+// The warning is produced once for a class template, not again for each
+// instantiation.
+template <typename T> struct W {
+  T v;
+  W &operator++();
+  W operator++(int) = default;
+  // pre2d-warning at -1 {{defaulted postfix increment and decrement operators are a C++2d extension}}
+  // compat-warning at -2 {{defaulted postfix increment and decrement operators are incompatible with C++ standards before C++2d}}
+  // cxx03-warning at -3 {{defaulted function definitions are a C++11 extension}}
+};
+void use_w(W<int> w, W<long> x) {
+  w++;
+  x++;
+}
+
+// Other functions still cannot be defaulted.
+struct Other {
+  void f() = default;
+  // pre20-error at -1 {{only special member functions may be defaulted}}
+  // cxx20-error at -2 {{only special member functions and comparison operators may be defaulted}}
+  // cxx2d-error at -3 {{only special member functions, comparison operators, and postfix increment and decrement operators may be defaulted}}
+  // cxx03-warning at -4 {{defaulted function definitions are a C++11 extension}}
+  Other &operator++() = default;
+  // expected-error at -1 {{only the postfix form of 'operator++' can be defaulted}}
+  // cxx03-warning at -2 {{defaulted function definitions are a C++11 extension}}
+};
+
+// An implicitly deleted defaulted postfix operator.
+struct Deleted {
+  Deleted operator++(int) = default; // #Deleted
+  // expected-warning@#Deleted {{explicitly defaulted postfix increment operator is implicitly deleted}}
+  // expected-note@#Deleted 2 {{defaulted 'operator++' is implicitly deleted because there is no viable prefix 'operator++' for an lvalue of type 'Deleted'}}
+  // expected-note@#Deleted {{replace 'default' with 'delete'}}
+  // expected-note@#Deleted {{explicitly defaulted function was implicitly deleted here}}
+  // pre2d-warning@#Deleted {{defaulted postfix increment and decrement operators are a C++2d extension}}
+  // compat-warning@#Deleted {{defaulted postfix increment and decrement operators are incompatible with C++ standards before C++2d}}
+  // cxx03-warning@#Deleted {{defaulted function definitions are a C++11 extension}}
+};
+void use_deleted(Deleted d) {
+  d++; // expected-error {{object of type 'Deleted' cannot be incremented because its defaulted postfix increment operator is implicitly deleted}}
+}
+
+#if __cplusplus >= 201402L
+// Before C++23, an explicitly-defaulted function may only be declared
+// constexpr if it is constexpr-compatible.
+struct NotConstexprCompatible {
+  NotConstexprCompatible &operator++(); // pre23-note {{non-constexpr prefix 'operator++' declared here}}
+  constexpr NotConstexprCompatible operator++(int) = default;
+  // pre23-error at -1 {{defaulted definition of postfix increment operator cannot be declared constexpr because it invokes a non-constexpr function}}
+  // pre2d-warning at -2 {{defaulted postfix increment and decrement operators are a C++2d extension}}
+  // compat-warning at -3 {{defaulted postfix increment and decrement operators are incompatible with C++ standards before C++2d}}
+};
+
+// A defaulted postfix operator is implicitly constexpr when it can be.
+struct Constexpr {
+  int v;
+  constexpr Constexpr &operator++() { ++v; return *this; }
+  Constexpr operator++(int) = default;
+  // pre2d-warning at -1 {{defaulted postfix increment and decrement operators are a C++2d extension}}
+  // compat-warning at -2 {{defaulted postfix increment and decrement operators are incompatible with C++ standards before C++2d}}
+};
+constexpr int use_constexpr() {
+  Constexpr c{1};
+  Constexpr a = c++;
+  return a.v * 10 + c.v;
+}
+static_assert(use_constexpr() == 12, "");
+#endif
diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html
index 61d0f38bce87a5..c9ae0696461722 100755
--- a/clang/www/cxx_status.html
+++ b/clang/www/cxx_status.html
@@ -117,7 +117,7 @@ <h2 id="cxx29">C++2d implementation status</h2>
 <tr>
   <td>Defaulting postfix increment and decrement operations</td>
   <td><a href="https://wg21.link/P3668R4">P3668R4</a></td>
-  <td class="none" align="center">No</td>
+  <td class="unreleased" align="center">Clang 24</td>
 </tr>
 <tr>
   <td>Adding restrictions to defaulted assignment operator functions</td>

>From ce09ad196e5bc6af60bc775bebc4b336048c45cf Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Thu, 8 Oct 2026 03:58:43 +0300
Subject: [PATCH 02/35] cover more test cases

---
 .../over.inc/over.inc.default/p3.cpp          | 73 +++++++++++++++++++
 1 file changed, 73 insertions(+)

diff --git a/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p3.cpp b/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p3.cpp
index 2f8d1d9788c672..d0532d3c648e2f 100644
--- a/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p3.cpp
+++ b/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p3.cpp
@@ -1,4 +1,16 @@
 // RUN: %clang_cc1 -std=c++2d -verify %s
+// RUN: %clang_cc1 -std=c++2d -funknown-anytype -DUNKNOWN_ANYTYPE -verify=anytype %s
+
+#ifdef UNKNOWN_ANYTYPE
+// The discarded-value prefix call must have a known type.
+struct UnknownPrefixResult {
+  __unknown_anytype operator++();
+  UnknownPrefixResult operator++(int) = default; // anytype-error {{'operator++' has unknown return type; cast the call to its declared return type}}
+};
+void unknown_prefix_result(UnknownPrefixResult u) {
+  u++; // anytype-note {{in defaulted postfix increment operator for 'UnknownPrefixResult' first required here}}
+}
+#else
 
 // C++2d [over.inc.default]p3:
 //   The implicit definition of a defaulted postfix increment or decrement
@@ -149,6 +161,66 @@ void deleted_move(DeletedMove d) {
   d++; // expected-note {{in defaulted postfix increment operator for 'DeletedMove' first required here}}
 }
 
+// [over.inc.default]p2 checks the copy from an lvalue of type C, but in the
+// implicit definition of a volatile member, c is an lvalue of type volatile C.
+struct VolatileAggregate {
+  volatile VolatileAggregate &operator++() volatile;
+  VolatileAggregate operator++(int) volatile = default; // expected-error {{excess elements in struct initializer}}
+};
+void volatile_aggregate(VolatileAggregate v) {
+  v++; // expected-note {{in defaulted postfix increment operator for 'VolatileAggregate' first required here}}
+}
+
+// Errors that overload resolution does not detect make the implicit definition
+// ill-formed rather than deleted.
+struct BadDefaultArgument {
+  BadDefaultArgument();
+  template <class T> BadDefaultArgument(T &, int = T::missing); // expected-error 2 {{no member named 'missing' in 'BadDefaultArgument'}}
+  BadDefaultArgument &operator++();
+  BadDefaultArgument operator++(int) = default; // expected-note 2 {{in instantiation of default function argument expression for 'BadDefaultArgument<BadDefaultArgument>' required here}}
+};
+void bad_default_argument(BadDefaultArgument b) {
+  b++; // expected-note 2 {{in defaulted postfix increment operator for 'BadDefaultArgument' first required here}}
+}
+
+struct Incomplete; // expected-note 2 {{forward declaration of 'Incomplete'}}
+struct IncompletePrefixResult {
+  Incomplete operator++(); // expected-note {{'operator++' declared here}}
+  IncompletePrefixResult operator++(int) = default; // expected-error {{calling 'operator++' with incomplete return type 'Incomplete'}}
+};
+void incomplete_prefix_result(IncompletePrefixResult i) {
+  i++; // expected-note {{in defaulted postfix increment operator for 'IncompletePrefixResult' first required here}}
+}
+
+struct DeletedDestructor {
+  ~DeletedDestructor() = delete; // expected-note {{'~DeletedDestructor' has been explicitly marked deleted here}}
+};
+struct DeletedDestructorPrefixResult {
+  DeletedDestructor operator--();
+  DeletedDestructorPrefixResult operator--(int) = default; // expected-error {{attempt to use a deleted function}}
+};
+void deleted_destructor_prefix_result(DeletedDestructorPrefixResult d) {
+  d--; // expected-note {{in defaulted postfix decrement operator for 'DeletedDestructorPrefixResult' first required here}}
+}
+
+// Uses of the first parameter in the implicit definition are checked as usual.
+struct UnavailableParam {
+  UnavailableParam &operator++();
+};
+UnavailableParam operator++(UnavailableParam &u __attribute__((unavailable)), int) = default; // expected-note 2 {{'u' has been explicitly marked unavailable here}}
+// expected-error at -1 2 {{'u' is unavailable}}
+// expected-note at -2 {{in defaulted postfix increment operator for 'UnavailableParam' first required here}}
+void unavailable_param(UnavailableParam u) { u++; }
+
+// The same errors are diagnosed when the body is only built to compute the
+// exception specification.
+struct IncompletePrefixResultNoexcept {
+  Incomplete operator++(); // expected-note {{'operator++' declared here}}
+  IncompletePrefixResultNoexcept operator++(int) = default; // expected-error {{calling 'operator++' with incomplete return type 'Incomplete'}}
+};
+extern IncompletePrefixResultNoexcept incomplete_prefix_result_noexcept;
+bool incomplete_noexcept = noexcept(incomplete_prefix_result_noexcept++); // expected-note {{in evaluation of exception specification for 'IncompletePrefixResultNoexcept::operator++' needed here}}
+
 // Attributes on the prefix operator are honored, as for any other call.
 struct Nodiscard {
   [[nodiscard]] Nodiscard &operator++();
@@ -261,3 +333,4 @@ struct NoexceptNonMember {
 NoexceptNonMember operator++(NoexceptNonMember &, int) = default;
 extern NoexceptNonMember noexcept_non_member;
 static_assert(noexcept(noexcept_non_member++));
+#endif

>From f4a3307628022302b4cdf0568bcfaea725408af7 Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Thu, 8 Oct 2026 04:00:57 +0300
Subject: [PATCH 03/35] Use `"__ugly"` identifiers.

---
 clang/lib/Sema/SemaDeclCXX.cpp                 |  2 +-
 .../ast-dump-defaulted-postfix-operators.cpp   | 18 +++++++++---------
 2 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 97a3114ca99a3a..c19a25ca20ebaf 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -9770,7 +9770,7 @@ static StmtResult buildDefaultedPostfixOperatorBody(Sema &S, FunctionDecl *FD,
   if (Init.isInvalid())
     return StmtError();
   QualType C = FD->getReturnType();
-  IdentifierInfo *TmpName = &S.Context.Idents.get("tmp");
+  IdentifierInfo *TmpName = &S.Context.Idents.get("__tmp");
   VarDecl *Tmp =
       VarDecl::Create(S.Context, S.CurContext, Loc, Loc, TmpName, C,
                       S.Context.getTrivialTypeSourceInfo(C, Loc), SC_None);
diff --git a/clang/test/AST/ast-dump-defaulted-postfix-operators.cpp b/clang/test/AST/ast-dump-defaulted-postfix-operators.cpp
index 3b9bec55202137..ced3d5d2ce539c 100644
--- a/clang/test/AST/ast-dump-defaulted-postfix-operators.cpp
+++ b/clang/test/AST/ast-dump-defaulted-postfix-operators.cpp
@@ -17,7 +17,7 @@ void use_member(S s) { s++; }
 // CHECK-NEXT:   ParmVarDecl {{.*}} 'int'
 // CHECK-NEXT:   CompoundStmt
 // CHECK-NEXT:     DeclStmt
-// CHECK-NEXT:       VarDecl {{.*}} used tmp 'S' nrvo callinit
+// CHECK-NEXT:       VarDecl {{.*}} used __tmp 'S' nrvo callinit
 // CHECK-NEXT:         CXXConstructExpr {{.*}} 'S' 'void (const S &) noexcept'
 // CHECK-NEXT:           ImplicitCastExpr {{.*}} 'const S' lvalue <NoOp>
 // CHECK-NEXT:             UnaryOperator {{.*}} 'S' lvalue prefix '*' cannot overflow
@@ -27,10 +27,10 @@ void use_member(S s) { s++; }
 // CHECK-NEXT:         DeclRefExpr {{.*}} 'S &()' lvalue CXXMethod {{.*}} 'operator++' 'S &()'
 // CHECK-NEXT:       UnaryOperator {{.*}} 'S' lvalue prefix '*' cannot overflow
 // CHECK-NEXT:         CXXThisExpr {{.*}} 'S *' this
-// CHECK-NEXT:     ReturnStmt {{.*}} nrvo_candidate(Var {{.*}} 'tmp' 'S')
+// CHECK-NEXT:     ReturnStmt {{.*}} nrvo_candidate(Var {{.*}} '__tmp' 'S')
 // CHECK-NEXT:       CXXConstructExpr {{.*}} 'S' 'void (S &&) noexcept'
 // CHECK-NEXT:         ImplicitCastExpr {{.*}} 'S' xvalue <NoOp>
-// CHECK-NEXT:           DeclRefExpr {{.*}} 'S' lvalue Var {{.*}} 'tmp' 'S'
+// CHECK-NEXT:           DeclRefExpr {{.*}} 'S' lvalue Var {{.*}} '__tmp' 'S'
 
 struct T {
   int v;
@@ -44,7 +44,7 @@ void use_non_member(T t) { t--; }
 // CHECK-NEXT:   ParmVarDecl {{.*}} 'int'
 // CHECK-NEXT:   CompoundStmt
 // CHECK-NEXT:     DeclStmt
-// CHECK-NEXT:       VarDecl {{.*}} used tmp 'T' nrvo callinit
+// CHECK-NEXT:       VarDecl {{.*}} used __tmp 'T' nrvo callinit
 // CHECK-NEXT:         CXXConstructExpr {{.*}} 'T' 'void (const T &) noexcept'
 // CHECK-NEXT:           ImplicitCastExpr {{.*}} 'const T' lvalue <NoOp>
 // CHECK-NEXT:             DeclRefExpr {{.*}} 'T' lvalue ParmVar {{.*}} 'self' 'T &'
@@ -52,10 +52,10 @@ void use_non_member(T t) { t--; }
 // CHECK-NEXT:       ImplicitCastExpr {{.*}} 'T &(*)()' <FunctionToPointerDecay>
 // CHECK-NEXT:         DeclRefExpr {{.*}} 'T &()' lvalue CXXMethod {{.*}} 'operator--' 'T &()'
 // CHECK-NEXT:       DeclRefExpr {{.*}} 'T' lvalue ParmVar {{.*}} 'self' 'T &'
-// CHECK-NEXT:     ReturnStmt {{.*}} nrvo_candidate(Var {{.*}} 'tmp' 'T')
+// CHECK-NEXT:     ReturnStmt {{.*}} nrvo_candidate(Var {{.*}} '__tmp' 'T')
 // CHECK-NEXT:       CXXConstructExpr {{.*}} 'T' 'void (T &&) noexcept'
 // CHECK-NEXT:         ImplicitCastExpr {{.*}} 'T' xvalue <NoOp>
-// CHECK-NEXT:           DeclRefExpr {{.*}} 'T' lvalue Var {{.*}} 'tmp' 'T'
+// CHECK-NEXT:           DeclRefExpr {{.*}} 'T' lvalue Var {{.*}} '__tmp' 'T'
 
 struct U {
   int v;
@@ -69,7 +69,7 @@ void use_explicit_object(U u) { u++; }
 // CHECK-NEXT:   ParmVarDecl {{.*}} 'int'
 // CHECK-NEXT:   CompoundStmt
 // CHECK-NEXT:     DeclStmt
-// CHECK-NEXT:       VarDecl {{.*}} used tmp 'U' nrvo callinit
+// CHECK-NEXT:       VarDecl {{.*}} used __tmp 'U' nrvo callinit
 // CHECK-NEXT:         CXXConstructExpr {{.*}} 'U' 'void (const U &) noexcept'
 // CHECK-NEXT:           ImplicitCastExpr {{.*}} 'const U' lvalue <NoOp>
 // CHECK-NEXT:             DeclRefExpr {{.*}} 'U' lvalue ParmVar {{.*}} 'self' 'U &'
@@ -77,10 +77,10 @@ void use_explicit_object(U u) { u++; }
 // CHECK-NEXT:       ImplicitCastExpr {{.*}} 'U &(*)()' <FunctionToPointerDecay>
 // CHECK-NEXT:         DeclRefExpr {{.*}} 'U &()' lvalue CXXMethod {{.*}} 'operator++' 'U &()'
 // CHECK-NEXT:       DeclRefExpr {{.*}} 'U' lvalue ParmVar {{.*}} 'self' 'U &'
-// CHECK-NEXT:     ReturnStmt {{.*}} nrvo_candidate(Var {{.*}} 'tmp' 'U')
+// CHECK-NEXT:     ReturnStmt {{.*}} nrvo_candidate(Var {{.*}} '__tmp' 'U')
 // CHECK-NEXT:       CXXConstructExpr {{.*}} 'U' 'void (U &&) noexcept'
 // CHECK-NEXT:         ImplicitCastExpr {{.*}} 'U' xvalue <NoOp>
-// CHECK-NEXT:           DeclRefExpr {{.*}} 'U' lvalue Var {{.*}} 'tmp' 'U'
+// CHECK-NEXT:           DeclRefExpr {{.*}} 'U' lvalue Var {{.*}} '__tmp' 'U'
 
 // A deleted defaulted postfix operator has no body.
 struct Deleted {

>From 2e10a137a2ebe92b3cd30d0c7f78885924717add Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Thu, 8 Oct 2026 04:06:04 +0300
Subject: [PATCH 04/35] Refactor exception specification handling for defaulted
 postfix operators.

---
 clang/lib/Sema/SemaDeclCXX.cpp                            | 5 -----
 .../CXX/over/over.oper/over.inc/over.inc.default/p3.cpp   | 8 ++++++++
 2 files changed, 8 insertions(+), 5 deletions(-)

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index c19a25ca20ebaf..9e248247a9a7ce 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -10105,11 +10105,6 @@ ComputeDefaultedPostfixOperatorExceptionSpec(Sema &S, SourceLocation Loc,
       ExceptSpec.CalledStmt(Body.get());
   }
 
-  // The implicit definition also destroys the temporary copy.
-  if (CXXRecordDecl *RD = FD->getReturnType()->getAsCXXRecordDecl())
-    if (CXXDestructorDecl *Dtor = S.LookupDestructor(RD))
-      ExceptSpec.CalledDecl(Loc, Dtor);
-
   return ExceptSpec;
 }
 
diff --git a/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p3.cpp b/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p3.cpp
index d0532d3c648e2f..64d3d6ad8638b6 100644
--- a/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p3.cpp
+++ b/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p3.cpp
@@ -316,6 +316,14 @@ struct ThrowingDestructor {
   ThrowingDestructor operator++(int) = default;
 };
 static_assert(!noexcept(ThrowingDestructor{}++));
+struct ThrowingDestructorDefined {
+  ThrowingDestructorDefined();
+  ~ThrowingDestructorDefined() noexcept(false);
+  ThrowingDestructorDefined &operator++() noexcept;
+  ThrowingDestructorDefined operator++(int) = default;
+};
+void throwing_destructor_defined(ThrowingDestructorDefined t) { t++; }
+static_assert(!noexcept(ThrowingDestructorDefined{}++));
 struct ExplicitNoexcept {
   ExplicitNoexcept &operator++();
   ExplicitNoexcept operator++(int) noexcept = default;

>From 9a9cefb08b93869cacf56f3902262645ee85a0db Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Thu, 8 Oct 2026 04:36:03 +0300
Subject: [PATCH 05/35] compact 1 byte

---
 clang/include/clang/AST/Decl.h | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index 34e7f4ea701cdd..3a270d916f1623 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -2133,8 +2133,9 @@ class FunctionDecl : public DeclaratorDecl,
   /// For a defaulted function, the kind of defaulted function that it is.
   class DefaultedFunctionKind {
     LLVM_PREFERRED_TYPE(CXXSpecialMemberKind)
-    unsigned SpecialMember : 8;
-    unsigned Comparison : 8;
+    unsigned SpecialMember : 3;
+    LLVM_PREFERRED_TYPE(DefaultedComparisonKind)
+    unsigned Comparison : 3;
     LLVM_PREFERRED_TYPE(PostfixOperatorKind)
     unsigned PostfixOperator : 2;
 

>From ff9fa36709cd35ad48994299be3711872d9f1230 Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Thu, 8 Oct 2026 04:46:54 +0300
Subject: [PATCH 06/35] delegate constructors to the default constructor

---
 clang/include/clang/AST/Decl.h | 20 +++++++++-----------
 1 file changed, 9 insertions(+), 11 deletions(-)

diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index 3a270d916f1623..b3d218bc0af7a8 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -2144,18 +2144,16 @@ class FunctionDecl : public DeclaratorDecl,
         : SpecialMember(llvm::to_underlying(CXXSpecialMemberKind::Invalid)),
           Comparison(llvm::to_underlying(DefaultedComparisonKind::None)),
           PostfixOperator(llvm::to_underlying(PostfixOperatorKind::None)) {}
-    DefaultedFunctionKind(CXXSpecialMemberKind CSM)
-        : SpecialMember(llvm::to_underlying(CSM)),
-          Comparison(llvm::to_underlying(DefaultedComparisonKind::None)),
-          PostfixOperator(llvm::to_underlying(PostfixOperatorKind::None)) {}
+    DefaultedFunctionKind(CXXSpecialMemberKind CSM) : DefaultedFunctionKind() {
+      SpecialMember = llvm::to_underlying(CSM);
+    }
     DefaultedFunctionKind(DefaultedComparisonKind Comp)
-        : SpecialMember(llvm::to_underlying(CXXSpecialMemberKind::Invalid)),
-          Comparison(llvm::to_underlying(Comp)),
-          PostfixOperator(llvm::to_underlying(PostfixOperatorKind::None)) {}
-    DefaultedFunctionKind(PostfixOperatorKind PO)
-        : SpecialMember(llvm::to_underlying(CXXSpecialMemberKind::Invalid)),
-          Comparison(llvm::to_underlying(DefaultedComparisonKind::None)),
-          PostfixOperator(llvm::to_underlying(PO)) {}
+        : DefaultedFunctionKind() {
+      Comparison = llvm::to_underlying(Comp);
+    }
+    DefaultedFunctionKind(PostfixOperatorKind PO) : DefaultedFunctionKind() {
+      PostfixOperator = llvm::to_underlying(PO);
+    }
 
     bool isSpecialMember() const {
       return static_cast<CXXSpecialMemberKind>(SpecialMember) !=

>From 67d6959d81cf52684948b4e9dc86acec46ae31a5 Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Thu, 8 Oct 2026 04:48:46 +0300
Subject: [PATCH 07/35] reorganize compatibility warnings

---
 clang/include/clang/Basic/DiagnosticSemaKinds.td | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index b2f72cab83243d..ffa178f6fe2098 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -88,8 +88,6 @@ defm equals_this_lambda_capture : CXX20Compat<
   "explicit capture of 'this' with a capture default of '=' is">;
 defm capture_binding : CXX20Compat<"captured structured bindings are">;
 defm defaulted_comparison : CXX20Compat<"defaulted comparison operators are">;
-defm defaulted_postfix_operator : CXX29Compat<
-  "defaulted postfix increment and decrement operators are">;
 
 // C++23 compatibility with C++20 and earlier.
 defm constexpr_static_var : CXX23Compat<
@@ -108,6 +106,8 @@ defm placeholder_var_definition : CXX26Compat<"placeholder variables are">;
 
 // C++29 compatibility with C++26 and earlier.
 defm pack_indexing_template : CXX29Compat<"pack indexing for template names is">;
+defm defaulted_postfix_operator : CXX29Compat<
+  "defaulted postfix increment and decrement operators are">;
 
 // Compatibility warnings duplicated across multiple language versions.
 foreach std = [14, 20, 23] in {

>From 45e5b75c1ab80f0e0131c82ebf9e47a0d8a06c1c Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Thu, 8 Oct 2026 04:51:16 +0300
Subject: [PATCH 08/35] rephrase comment

---
 clang/lib/AST/Decl.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index aa0f37aa1a782e..e93507140ac6b7 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -3312,7 +3312,7 @@ FunctionDecl::getDefaultedFunctionKind() const {
   case OO_MinusMinus: {
     // Only the postfix forms can be defaulted. They are distinguished from the
     // prefix forms by their trailing 'int' parameter, so they have exactly two
-    // parameters once the implicit object parameter (if any) is counted.
+    // parameters including implicit or explicit 'this'.
     unsigned NumParams = getNumParams();
     if (const auto *MD = dyn_cast<CXXMethodDecl>(this);
         MD && MD->isImplicitObjectMemberFunction())

>From d0a41a0ba6a2d2d938c43e7f5e68c173b7b6d060 Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Thu, 8 Oct 2026 05:16:59 +0300
Subject: [PATCH 09/35] use `enum_select`

---
 .../clang/Basic/DiagnosticCommonKinds.td      |  8 +--
 .../clang/Basic/DiagnosticSemaKinds.td        |  8 ++-
 clang/lib/Parse/ParseDecl.cpp                 |  6 ++-
 clang/lib/Parse/ParseDeclCXX.cpp              |  6 ++-
 clang/lib/Sema/SemaDeclCXX.cpp                | 54 ++++++++-----------
 5 files changed, 42 insertions(+), 40 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticCommonKinds.td b/clang/include/clang/Basic/DiagnosticCommonKinds.td
index 2a503cd76e0eba..d1b62ac13fd23d 100644
--- a/clang/include/clang/Basic/DiagnosticCommonKinds.td
+++ b/clang/include/clang/Basic/DiagnosticCommonKinds.td
@@ -97,9 +97,11 @@ def err_invalid_storage_class_in_func_decl : Error<
   "invalid storage class specifier in function declarator">;
 def err_expected_namespace_name : Error<"expected namespace name">;
 def err_default_special_members : Error<
-  "only special member functions%select{| and comparison operators|, "
-  "comparison operators, and postfix increment and decrement operators}0 "
-  "may be defaulted">;
+  "only special member functions"
+  "%enum_select<DefaultableFunctionKinds>{%SpecialMembers{}|"
+  "%Comparisons{ and comparison operators}|"
+  "%PostfixOperators{, comparison operators, and postfix increment and "
+  "decrement operators}}0 may be defaulted">;
 def err_deleted_non_function : Error<
   "only functions can have deleted definitions">;
 def err_module_not_found : Error<"module '%0' not found">, DefaultFatal;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index ffa178f6fe2098..d5917d12857da7 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10720,6 +10720,8 @@ def note_defaulted_postfix_operator_ambiguous : Note<
   "defaulted %0 is implicitly deleted because %select{copy construction of|"
   "prefix 'operator++' for|prefix 'operator--' for}1 an lvalue of type %2 "
   "is ambiguous">;
+// The %select indices of the operations in the following notes are given by
+// diag::DefaultedPostfixOperatorStep.
 def note_defaulted_postfix_operator_calls_deleted : Note<
   "defaulted %0 is implicitly deleted because it would invoke a deleted "
   "%select{constructor|destructor|prefix 'operator++'|prefix 'operator--'}1">;
@@ -10732,8 +10734,10 @@ def err_defaulted_postfix_operator_constexpr_mismatch : Error<
   "cannot be declared %select{constexpr|consteval}1 because it invokes a "
   "non-constexpr function">;
 def note_defaulted_postfix_operator_not_constexpr_here : Note<
-  "non-constexpr %select{constructor|destructor|prefix 'operator++'|"
-  "prefix 'operator--'|conversion function}0 declared here">;
+  "non-constexpr %enum_select<DefaultedPostfixOperatorStep>{"
+  "%Constructor{constructor}|%Destructor{destructor}|"
+  "%PrefixIncrement{prefix 'operator++'}|%PrefixDecrement{prefix 'operator--'}|"
+  "%ConversionFunction{conversion function}}0 declared here">;
 def err_ovl_deleted_postfix_operator : Error<
   "object of type %0 cannot be %select{<ERROR>|incremented|decremented}1 "
   "because its defaulted %sub{select_defaulted_postfix_operator_kind}1 is "
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index c3da1f50ba23fe..691fb814d370c2 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -2628,7 +2628,11 @@ Decl *Parser::ParseDeclarationAfterDeclaratorAndAttributes(
           << 0 /* default */;
       else
         Diag(ConsumeToken(), diag::err_default_special_members)
-            << (getLangOpts().CPlusPlus29 ? 2 : getLangOpts().CPlusPlus20);
+            << (getLangOpts().CPlusPlus29
+                    ? diag::DefaultableFunctionKinds::PostfixOperators
+                : getLangOpts().CPlusPlus20
+                    ? diag::DefaultableFunctionKinds::Comparisons
+                    : diag::DefaultableFunctionKinds::SpecialMembers);
     } else {
       InitializerScopeRAII InitScope(*this, D, ThisDecl);
 
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 644b2e1f180dc3..d34051caa925f6 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -3377,7 +3377,11 @@ ExprResult Parser::ParseCXXMemberInitializer(Decl *D, bool IsFunction,
             << 0 /* default */;
       else
         Diag(ConsumeToken(), diag::err_default_special_members)
-            << (getLangOpts().CPlusPlus29 ? 2 : getLangOpts().CPlusPlus20);
+            << (getLangOpts().CPlusPlus29
+                    ? diag::DefaultableFunctionKinds::PostfixOperators
+                : getLangOpts().CPlusPlus20
+                    ? diag::DefaultableFunctionKinds::Comparisons
+                    : diag::DefaultableFunctionKinds::SpecialMembers);
       return ExprError();
     }
   }
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 9e248247a9a7ce..9b60451d0de3a0 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -9505,18 +9505,6 @@ ComputeDefaultedComparisonExceptionSpec(Sema &S, SourceLocation Loc,
 //===----------------------------------------------------------------------===//
 
 namespace {
-/// The operations performed by the implicit definition of a defaulted postfix
-/// increment or decrement operator function. The values correspond to %select
-/// indices in the diagnostics explaining why such a function is deleted or is
-/// not constexpr.
-enum class PostfixOperatorStep {
-  Constructor,
-  Destructor,
-  PrefixIncrement,
-  PrefixDecrement,
-  ConversionFunction
-};
-
 /// Which diagnostics to produce while analyzing a defaulted postfix increment
 /// or decrement operator function.
 enum class PostfixOperatorDiagnoseKind {
@@ -9578,29 +9566,28 @@ analyzeDefaultedPostfixOperator(Sema &S, FunctionDecl *FD,
     Info.Deleted = true;
     return Info;
   };
-  auto InaccessibleDiag = [&](PostfixOperatorStep Step) {
+  auto InaccessibleDiag = [&](unsigned Step) {
     return Diagnose == DiagnoseKind::ExplainDeleted
                ? S.PDiag(diag::note_defaulted_postfix_operator_inaccessible)
-                     << FD << (int)Step
+                     << FD << Step
                : S.PDiag();
   };
-  auto NoteCallsDeleted = [&](PostfixOperatorStep Step, FunctionDecl *Callee) {
+  auto NoteCallsDeleted = [&](unsigned Step, FunctionDecl *Callee) {
     if (Diagnose != DiagnoseKind::ExplainDeleted)
       return;
     S.Diag(Loc, diag::note_defaulted_postfix_operator_calls_deleted)
-        << FD << (int)Step;
+        << FD << Step;
     if (Callee)
       S.NoteDeletedFunction(Callee);
   };
-  auto CheckConstexpr = [&](const FunctionDecl *Callee,
-                            PostfixOperatorStep Step) {
+  auto CheckConstexpr = [&](const FunctionDecl *Callee, unsigned Step) {
     if (Callee->isConstexpr())
       return;
     Info.Constexpr = false;
     if (Diagnose == DiagnoseKind::ExplainConstexpr)
       S.Diag(Callee->getLocation(),
              diag::note_defaulted_postfix_operator_not_constexpr_here)
-          << (int)Step;
+          << Step;
   };
 
   // C++2d [over.inc.default]p2:
@@ -9626,7 +9613,7 @@ analyzeDefaultedPostfixOperator(Sema &S, FunctionDecl *FD,
         OverloadCandidateSet::iterator Best;
         switch (OR) {
         case OR_Deleted:
-          NoteCallsDeleted(PostfixOperatorStep::Constructor,
+          NoteCallsDeleted(diag::DefaultedPostfixOperatorStep::Constructor,
                            Cands.BestViableFunction(S, Loc, Best) == OR_Deleted
                                ? Best->Function
                                : nullptr);
@@ -9655,21 +9642,23 @@ analyzeDefaultedPostfixOperator(Sema &S, FunctionDecl *FD,
         continue;
       if (!S.isMemberAccessibleForDeletion(
               RD, Step.Function.FoundDecl, C, Loc,
-              InaccessibleDiag(PostfixOperatorStep::Constructor)))
+              InaccessibleDiag(
+                  diag::DefaultedPostfixOperatorStep::Constructor)))
         return Deleted();
-      CheckConstexpr(Step.Function.Function, PostfixOperatorStep::Constructor);
+      CheckConstexpr(Step.Function.Function,
+                     diag::DefaultedPostfixOperatorStep::Constructor);
     }
 
     //  -- C has a destructor that is deleted or inaccessible from the context
     //     of the function-body, or
     if (CXXDestructorDecl *Dtor = S.LookupDestructor(RD)) {
       if (Dtor->isDeleted()) {
-        NoteCallsDeleted(PostfixOperatorStep::Destructor, Dtor);
+        NoteCallsDeleted(diag::DefaultedPostfixOperatorStep::Destructor, Dtor);
         return Deleted();
       }
       if (!S.isMemberAccessibleForDeletion(
               RD, DeclAccessPair::make(Dtor, Dtor->getAccess()), C, Loc,
-              InaccessibleDiag(PostfixOperatorStep::Destructor)))
+              InaccessibleDiag(diag::DefaultedPostfixOperatorStep::Destructor)))
         return Deleted();
     }
   }
@@ -9678,9 +9667,9 @@ analyzeDefaultedPostfixOperator(Sema &S, FunctionDecl *FD,
   //     postfix increment operator function or --c for a postfix decrement
   //     operator function does not result in a usable candidate.
   bool IsIncrement = Kind == PostfixOperatorKind::Increment;
-  PostfixOperatorStep PrefixStep = IsIncrement
-                                       ? PostfixOperatorStep::PrefixIncrement
-                                       : PostfixOperatorStep::PrefixDecrement;
+  unsigned PrefixStep =
+      IsIncrement ? diag::DefaultedPostfixOperatorStep::PrefixIncrement
+                  : diag::DefaultedPostfixOperatorStep::PrefixDecrement;
   UnresolvedSet<16> Fns;
   if (auto *DFI = FD->getDefaultedOrDeletedInfo())
     Fns.assign(DFI->getUnqualifiedLookups().begin(),
@@ -9705,7 +9694,7 @@ analyzeDefaultedPostfixOperator(Sema &S, FunctionDecl *FD,
                Best->Conversions[0].isUserDefined()) {
       // A built-in candidate, reached through a conversion function.
       CheckConstexpr(Best->Conversions[0].UserDefined.ConversionFunction,
-                     PostfixOperatorStep::ConversionFunction);
+                     diag::DefaultedPostfixOperatorStep::ConversionFunction);
     }
     break;
 
@@ -19587,13 +19576,12 @@ void Sema::SetDeclDefaulted(Decl *Dcl, SourceLocation DefaultLoc, Scope *S) {
   if (!Dcl || Dcl->isInvalidDecl())
     return;
 
-  // The %select index for err_default_special_members: which kinds of
-  // functions can be defaulted in the current language mode.
-  unsigned DefaultableKinds = 0;
+  // Which kinds of functions can be defaulted in the current language mode.
+  unsigned DefaultableKinds = diag::DefaultableFunctionKinds::SpecialMembers;
   if (getLangOpts().CPlusPlus29)
-    DefaultableKinds = 2;
+    DefaultableKinds = diag::DefaultableFunctionKinds::PostfixOperators;
   else if (getLangOpts().CPlusPlus20)
-    DefaultableKinds = 1;
+    DefaultableKinds = diag::DefaultableFunctionKinds::Comparisons;
 
   auto *FD = dyn_cast<FunctionDecl>(Dcl);
   if (!FD) {

>From 0a8d6e5780db3522ef42d2a526c484edf23b6a82 Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Thu, 8 Oct 2026 05:24:49 +0300
Subject: [PATCH 10/35] clarify lvalue comment

---
 clang/lib/Sema/SemaDeclCXX.cpp | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 9b60451d0de3a0..9b144de09734f9 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -9558,7 +9558,9 @@ analyzeDefaultedPostfixOperator(Sema &S, FunctionDecl *FD,
   //   context equivalent to its function-body.
   Sema::ContextRAII SavedContext(S, FD);
 
-  // Let c be an lvalue of type C.
+  // Let c be an lvalue of type C. c stands for the first parameter (or the
+  // implicit object), which is always an lvalue when named in the function
+  // body, even if it is declared as an rvalue reference.
   OpaqueValueExpr Obj(Loc, C, VK_LValue);
   Expr *Args[] = {&Obj};
 

>From a48b32fe5ce22048fc50c35d1ffb64446ed7365e Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Thu, 8 Oct 2026 05:26:28 +0300
Subject: [PATCH 11/35] clarify 0

---
 clang/lib/Sema/SemaDeclCXX.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 9b144de09734f9..82a930f1280596 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -9624,7 +9624,7 @@ analyzeDefaultedPostfixOperator(Sema &S, FunctionDecl *FD,
           Cands.NoteCandidates(
               PartialDiagnosticAt(
                   Loc, S.PDiag(diag::note_defaulted_postfix_operator_ambiguous)
-                           << FD << 0 << C),
+                           << FD << /*copy construction*/ 0 << C),
               S, OCD_AmbiguousCandidates, Args, /*Opc=*/"", Loc);
           break;
         case OR_Success:
@@ -9632,7 +9632,7 @@ analyzeDefaultedPostfixOperator(Sema &S, FunctionDecl *FD,
           Cands.NoteCandidates(
               PartialDiagnosticAt(
                   Loc, S.PDiag(diag::note_defaulted_postfix_operator_no_viable)
-                           << FD << 0 << C),
+                           << FD << /*copy construction*/ 0 << C),
               S, OCD_AllCandidates, Args, /*Opc=*/"", Loc);
           break;
         }

>From f4a3ec1e917dc4aec1486e6f8c91f44be26b84e1 Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Thu, 8 Oct 2026 05:29:34 +0300
Subject: [PATCH 12/35] Reintroduce OR_Success case in
 analyzeDefaultedPostfixOperator with llvm_unreachable for initialization
 sequence validation.

---
 clang/lib/Sema/SemaDeclCXX.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 82a930f1280596..cb940edebdf29d 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -9627,7 +9627,6 @@ analyzeDefaultedPostfixOperator(Sema &S, FunctionDecl *FD,
                            << FD << /*copy construction*/ 0 << C),
               S, OCD_AmbiguousCandidates, Args, /*Opc=*/"", Loc);
           break;
-        case OR_Success:
         case OR_No_Viable_Function:
           Cands.NoteCandidates(
               PartialDiagnosticAt(
@@ -9635,6 +9634,8 @@ analyzeDefaultedPostfixOperator(Sema &S, FunctionDecl *FD,
                            << FD << /*copy construction*/ 0 << C),
               S, OCD_AllCandidates, Args, /*Opc=*/"", Loc);
           break;
+        case OR_Success:
+          llvm_unreachable("initialization sequence did not fail");
         }
       }
       return Deleted();

>From 89dfa402f700016b13eab54c58adfcbe8440c303 Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Thu, 8 Oct 2026 05:45:47 +0300
Subject: [PATCH 13/35] Enhance comment clarity in
 CheckExplicitlyDefaultedPostfixOperator regarding lookup scope and its
 equivalence to function-body context.

---
 clang/lib/Sema/SemaDeclCXX.cpp | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index cb940edebdf29d..e3fb032b091a31 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -9827,7 +9827,14 @@ bool Sema::CheckExplicitlyDefaultedPostfixOperator(Scope *S, FunctionDecl *FD,
   //   Name lookups and access checks in the implicit definition of a defaulted
   //   postfix increment or decrement operator function are performed from a
   //   context equivalent to its function-body.
+  // The lookup is performed from S, not from CurContext. S is either the body
+  // scope of an out-of-line definition, or the scope of the class in which the
+  // function is defaulted on its first declaration; for a lookup that ignores
+  // member functions, both are equivalent to the function-body.
   if (S) {
+    assert((S->getEntity() == FD ||
+            S->getEntity() == FD->getLexicalDeclContext()) &&
+           "lookup scope is not equivalent to the function-body");
     UnresolvedSet<16> Operators;
     LookupOverloadedOperatorName(IsIncrement ? OO_PlusPlus : OO_MinusMinus, S,
                                  Operators);

>From ba61f4df39516a3c70bdb2d7556eae16f143ba67 Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Thu, 8 Oct 2026 06:25:44 +0300
Subject: [PATCH 14/35] Rewrite logic for determining C type

---
 .../clang/Basic/DiagnosticSemaKinds.td        |   3 +
 clang/lib/Sema/SemaDeclCXX.cpp                | 107 +++++++++++-------
 .../over.inc/over.inc.default/p1.cpp          |  13 ++-
 3 files changed, 76 insertions(+), 47 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index d5917d12857da7..4d06e8cd763eba 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10700,6 +10700,9 @@ def err_defaulted_postfix_operator_param_unknown : Error<
 def err_defaulted_postfix_operator_return_type : Error<
   "return type for defaulted %sub{select_defaulted_postfix_operator_kind}0 "
   "must be %1, not %2">;
+def err_defaulted_postfix_operator_deduced_return_type : Error<
+  "defaulted %sub{select_defaulted_postfix_operator_kind}0 cannot have a "
+  "deduced return type">;
 def err_defaulted_postfix_operator_incomplete : Error<
   "cannot default %sub{select_defaulted_postfix_operator_kind}0 for "
   "incomplete type %1">;
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index e3fb032b091a31..5a3cec3094d43c 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -9857,54 +9857,73 @@ bool Sema::CheckExplicitlyDefaultedPostfixOperator(Scope *S, FunctionDecl *FD,
   QualType FirstParamType = getPostfixOperatorFirstParamType(FD);
   QualType ReturnType = FD->getDeclaredReturnType();
 
-  // Determine the type C. For a member function, this is the enclosing class.
-  // For a non-member function, infer it from the first parameter, falling back
-  // to the return type if the first parameter is not a reference.
-  QualType C;
-  if (MD)
-    C = Context.getCanonicalTagType(MD->getParent());
-  else if (FirstParamType->isReferenceType())
-    C = FirstParamType.getNonReferenceType().getUnqualifiedType();
-  else if (!ReturnType->isReferenceType())
-    C = ReturnType.getUnqualifiedType();
-  if (C.isNull() || (!MD && !C->isDependentType() && !C->isRecordType() &&
-                     !C->isEnumeralType())) {
-    Diag(FirstParam->getBeginLoc(),
-         diag::err_defaulted_postfix_operator_param_unknown)
-        << (int)Kind << FirstParamType << FirstParam->getSourceRange();
+  // Diagnose an invalid first parameter. ExpectedC is the type that the
+  // parameter should be a reference to, if that type is known.
+  auto DiagnoseFirstParam = [&](QualType ExpectedC) {
+    if (MD && MD->isImplicitObjectMemberFunction()) {
+      // The implicit object parameter is always a reference to the enclosing
+      // class, so the only possible problem is a const qualifier.
+      Diag(MD->getLocation(), diag::err_defaulted_postfix_operator_const)
+          << (int)Kind;
+    } else if (ExpectedC.isNull()) {
+      Diag(FirstParam->getBeginLoc(),
+           diag::err_defaulted_postfix_operator_param_unknown)
+          << (int)Kind << FirstParamType << FirstParam->getSourceRange();
+    } else {
+      Diag(FirstParam->getBeginLoc(),
+           diag::err_defaulted_postfix_operator_param)
+          << (int)Kind << !MD << FirstParamType
+          << Context.getLValueReferenceType(ExpectedC)
+          << Context.getLValueReferenceType(ExpectedC.withVolatile())
+          << FirstParam->getSourceRange();
+    }
     return true;
-  }
+  };
 
-  // Check the type of the first parameter.
-  if (!FirstParamType->isDependentType() && !C->isDependentType()) {
-    QualType Referent = FirstParamType->isReferenceType()
-                            ? FirstParamType.getNonReferenceType()
-                            : QualType();
-    if (Referent.isNull() || Referent.isConstQualified() ||
-        !Context.hasSameType(Referent.getUnqualifiedType(), C)) {
-      if (MD && MD->isImplicitObjectMemberFunction()) {
-        // The implicit object parameter is always a reference to the class,
-        // so the only possible problem is a const qualifier.
-        Diag(MD->getLocation(), diag::err_defaulted_postfix_operator_const)
-            << (int)Kind;
-      } else {
-        Diag(FirstParam->getBeginLoc(),
-             diag::err_defaulted_postfix_operator_param)
-            << (int)Kind << !MD << FirstParamType
-            << Context.getLValueReferenceType(C)
-            << Context.getLValueReferenceType(C.withVolatile())
-            << FirstParam->getSourceRange();
-      }
-      return true;
+  // Check the type of the first parameter and determine the type C from it.
+  // If the parameter type is dependent, C is not known until instantiation.
+  QualType C;
+  if (!FirstParamType->isDependentType()) {
+    QualType EnclosingClass =
+        MD ? Context.getCanonicalTagType(MD->getParent()) : QualType();
+
+    // The first parameter must be a reference to C.
+    if (!FirstParamType->isReferenceType())
+      return DiagnoseFirstParam(EnclosingClass);
+    QualType Referent = FirstParamType.getNonReferenceType();
+    C = Referent.getUnqualifiedType();
+
+    // For a member function, C is the enclosing class. For a non-member
+    // function, C must be a class or enumeration type.
+    if (MD) {
+      if (!Context.hasSameType(C, EnclosingClass))
+        return DiagnoseFirstParam(EnclosingClass);
+    } else if (!C->isRecordType() && !C->isEnumeralType()) {
+      return DiagnoseFirstParam(QualType());
     }
+
+    // The referent may be volatile-qualified, but not const-qualified.
+    if (Referent.isConstQualified())
+      return DiagnoseFirstParam(C);
+  }
+
+  // A deduced return type is never permitted, so diagnose it even if C is not
+  // known yet. Allowing it would need P2952R2 (auto& operator=(X&&) = default),
+  // which is not part of the standard.
+  if (ReturnType->getContainedDeducedType()) {
+    Diag(FD->getLocation(),
+         diag::err_defaulted_postfix_operator_deduced_return_type)
+        << (int)Kind << FD->getReturnTypeSourceRange();
+    return true;
   }
 
-  // Check the declared return type. A placeholder type is not permitted; this
-  // would need P2952R2 (auto& operator=(X&&) = default), which is not yet
-  // part of the standard.
-  if (ReturnType->getContainedDeducedType() ||
-      (!ReturnType->isDependentType() && !C->isDependentType() &&
-       !Context.hasSameType(ReturnType, C))) {
+  // If C is not known or the return type is dependent, defer the remaining
+  // checks until instantiation.
+  if (C.isNull() || ReturnType->isDependentType())
+    return false;
+
+  //    -- has a declared return type of C.
+  if (!Context.hasSameType(ReturnType, C)) {
     Diag(FD->getLocation(), diag::err_defaulted_postfix_operator_return_type)
         << (int)Kind << C << ReturnType << FD->getReturnTypeSourceRange();
     return true;
@@ -9912,7 +9931,7 @@ bool Sema::CheckExplicitlyDefaultedPostfixOperator(Scope *S, FunctionDecl *FD,
 
   // For a defaulted function in a dependent context, defer all remaining
   // checks until instantiation.
-  if (C->isDependentType() || FD->isDependentContext())
+  if (FD->isDependentContext())
     return false;
 
   //    -- is defined as defaulted in C or in a context where C is complete
diff --git a/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p1.cpp b/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p1.cpp
index 86a2620fbd7944..c970eb122da502 100644
--- a/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p1.cpp
+++ b/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p1.cpp
@@ -76,11 +76,11 @@ struct ReturnsConst {
 };
 struct ReturnsAuto {
   ReturnsAuto &operator++();
-  auto operator++(int) = default; // expected-error {{return type for defaulted postfix increment operator must be 'ReturnsAuto', not 'auto'}}
+  auto operator++(int) = default; // expected-error {{defaulted postfix increment operator cannot have a deduced return type}}
 };
 struct ReturnsDecltypeAuto {
   ReturnsDecltypeAuto &operator--();
-  decltype(auto) operator--(int) = default; // expected-error {{return type for defaulted postfix decrement operator must be 'ReturnsDecltypeAuto', not 'decltype(auto)'}}
+  decltype(auto) operator--(int) = default; // expected-error {{defaulted postfix decrement operator cannot have a deduced return type}}
 };
 struct ReturnsInt {
   ReturnsInt &operator++();
@@ -110,7 +110,7 @@ NV operator++(volatile NV &, int) = default; // OK
 struct NConst { NConst &operator++(); };
 NConst operator++(const NConst &, int) = default; // expected-error {{invalid first parameter type for defaulted postfix increment operator; found 'const NConst &', expected 'NConst &' or 'volatile NConst &'}}
 struct NValue { NValue &operator++(); };
-NValue operator++(NValue, int) = default; // expected-error {{invalid first parameter type for defaulted postfix increment operator; found 'NValue', expected 'NValue &' or 'volatile NValue &'}}
+NValue operator++(NValue, int) = default; // expected-error {{invalid first parameter type for defaulted non-member postfix increment operator; found 'NValue', expected reference to a non-const class or enumeration type}}
 struct NRet { NRet &operator--(); };
 int operator--(NRet &, int) = default; // expected-error {{return type for defaulted postfix decrement operator must be 'NRet', not 'int'}}
 struct NMismatch { NMismatch &operator++(); };
@@ -203,3 +203,10 @@ template <typename T> struct DependentFriend {
   friend int operator--(T &, int) = default;                           // expected-error {{return type for defaulted postfix decrement operator must be 'S', not 'int'}}
 };
 DependentFriend<S> df1; // expected-note {{in instantiation of template class 'DependentFriend<S>' requested here}}
+
+// A deduced return type is never valid, so it is diagnosed even if C is not
+// known yet.
+template <typename T> struct DependentAuto {
+  friend auto operator++(T &, int) = default; // expected-error {{defaulted postfix increment operator cannot have a deduced return type}}
+  auto operator--(this T &, int) = default;   // expected-error {{defaulted postfix decrement operator cannot have a deduced return type}}
+};

>From 209f61da98b930d1b91ef6959c9103fba1d6d56b Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Thu, 8 Oct 2026 06:27:06 +0300
Subject: [PATCH 15/35] Fix logic for determining the first declaration in
 CheckExplicitlyDefaultedPostfixOperator by using getFirstDecl instead of
 getCanonicalDecl.

---
 clang/lib/Sema/SemaDeclCXX.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 5a3cec3094d43c..f768fceb7dc998 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -9944,7 +9944,7 @@ bool Sema::CheckExplicitlyDefaultedPostfixOperator(Scope *S, FunctionDecl *FD,
   DefaultedPostfixOperatorInfo Info = analyzeDefaultedPostfixOperator(
       *this, FD, Kind, C, PostfixOperatorDiagnoseKind::NoDiagnostics);
 
-  bool First = FD == FD->getCanonicalDecl();
+  bool First = FD == FD->getFirstDecl();
 
   if (!First) {
     if (Info.Deleted) {

>From a7a446917ac31fd7cac9dfcb90944e2f75072e1c Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Thu, 8 Oct 2026 06:37:42 +0300
Subject: [PATCH 16/35] Refactor defaulted function handling by introducing a
 helper to define functions with synthesized bodies. Update exception
 specification computation to utilize this new helper for better code clarity
 and maintainability.

---
 clang/lib/Sema/SemaDeclCXX.cpp | 140 ++++++++++++++-------------------
 1 file changed, 57 insertions(+), 83 deletions(-)

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index f768fceb7dc998..c5844d3fdfd9a6 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -9412,65 +9412,80 @@ void Sema::DeclareImplicitEqualityComparison(CXXRecordDecl *RD,
   popCodeSynthesisContext();
 }
 
-void Sema::DefineDefaultedComparison(SourceLocation UseLoc, FunctionDecl *FD,
-                                     DefaultedComparisonKind DCK) {
+/// Define a defaulted function whose body is synthesized by \p BuildBody: a
+/// defaulted comparison operator function or a defaulted postfix increment or
+/// decrement operator function.
+static void defineDefaultedFunctionWithSynthesizedBody(
+    Sema &S, SourceLocation UseLoc, FunctionDecl *FD,
+    llvm::function_ref<StmtResult(SourceLocation BodyLoc)> BuildBody) {
   assert(FD->isDefaulted() && !FD->isDeleted() &&
          !FD->doesThisDeclarationHaveABody());
   if (FD->willHaveBody() || FD->isInvalidDecl())
     return;
 
-  SynthesizedFunctionScope Scope(*this, FD);
+  Sema::SynthesizedFunctionScope Scope(S, FD);
 
   // Add a context note for diagnostics produced after this point.
   Scope.addContextNote(UseLoc);
 
-  DefaultedFunctionFPFeaturesRAII RestoreFP(*this, FD);
+  DefaultedFunctionFPFeaturesRAII RestoreFP(S, FD);
 
   {
     // Build and set up the function body.
-    // The first parameter has type maybe-ref-to maybe-const T, use that to get
-    // the type of the class being compared.
-    auto PT = FD->getParamDecl(0)->getType();
-    CXXRecordDecl *RD = PT.getNonReferenceType()->getAsCXXRecordDecl();
     SourceLocation BodyLoc =
         FD->getEndLoc().isValid() ? FD->getEndLoc() : FD->getLocation();
-    StmtResult Body =
-        DefaultedComparisonSynthesizer(*this, RD, FD, DCK, BodyLoc).build();
+    StmtResult Body = BuildBody(BodyLoc);
     if (Body.isInvalid()) {
       FD->setInvalidDecl();
       return;
     }
     FD->setBody(Body.get());
-    FD->markUsed(Context);
+    FD->markUsed(S.Context);
   }
 
   // The exception specification is needed because we are defining the
   // function. Note that this will reuse the body we just built.
-  ResolveExceptionSpec(UseLoc, FD->getType()->castAs<FunctionProtoType>());
+  S.ResolveExceptionSpec(UseLoc, FD->getType()->castAs<FunctionProtoType>());
 
-  if (ASTMutationListener *L = getASTMutationListener())
+  if (ASTMutationListener *L = S.getASTMutationListener())
     L->CompletedImplicitDefinition(FD);
 }
 
+void Sema::DefineDefaultedComparison(SourceLocation UseLoc, FunctionDecl *FD,
+                                     DefaultedComparisonKind DCK) {
+  defineDefaultedFunctionWithSynthesizedBody(
+      *this, UseLoc, FD, [&](SourceLocation BodyLoc) {
+        // The first parameter has type maybe-ref-to maybe-const T, use that to
+        // get the type of the class being compared.
+        auto PT = FD->getParamDecl(0)->getType();
+        CXXRecordDecl *RD = PT.getNonReferenceType()->getAsCXXRecordDecl();
+        return DefaultedComparisonSynthesizer(*this, RD, FD, DCK, BodyLoc)
+            .build();
+      });
+}
+
+/// Compute the exception specification of a defaulted function whose body is
+/// synthesized by \p BuildBody: a defaulted comparison operator function or a
+/// defaulted postfix increment or decrement operator function.
 static Sema::ImplicitExceptionSpecification
-ComputeDefaultedComparisonExceptionSpec(Sema &S, SourceLocation Loc,
-                                        FunctionDecl *FD,
-                                        DefaultedComparisonKind DCK) {
+computeExceptionSpecFromSynthesizedBody(
+    Sema &S, SourceLocation Loc, FunctionDecl *FD,
+    llvm::function_ref<StmtResult(SourceLocation BodyLoc)> BuildBody) {
   ComputingExceptionSpec CES(S, FD, Loc);
   Sema::ImplicitExceptionSpecification ExceptSpec(S);
 
   if (FD->isInvalidDecl())
     return ExceptSpec;
 
-  // The common case is that we just defined the comparison function. In that
-  // case, just look at whether the body can throw.
+  // The common case is that we just defined the function. In that case, just
+  // look at whether the body can throw.
   if (Stmt *FunctionBody = FD->getBody()) {
     ExceptSpec.CalledStmt(FunctionBody);
   } else {
     // Otherwise, build a body so we can check it. This should ideally only
     // happen when we're not actually marking the function referenced. (This is
     // only really important for efficiency: we don't want to build and throw
-    // away bodies for comparison functions more than we strictly need to.)
+    // away bodies for defaulted functions more than we strictly need to.)
 
     // Pretend to synthesize the function body in an unevaluated context.
     // Note that we can't actually just go ahead and define the function here:
@@ -9479,14 +9494,9 @@ ComputeDefaultedComparisonExceptionSpec(Sema &S, SourceLocation Loc,
     EnterExpressionEvaluationContext Context(
         S, Sema::ExpressionEvaluationContext::Unevaluated);
 
-    CXXRecordDecl *RD =
-        cast<CXXRecordDecl>(FD->getFriendObjectKind() == Decl::FOK_None
-                                ? FD->getDeclContext()
-                                : FD->getLexicalDeclContext());
     SourceLocation BodyLoc =
         FD->getEndLoc().isValid() ? FD->getEndLoc() : FD->getLocation();
-    StmtResult Body =
-        DefaultedComparisonSynthesizer(S, RD, FD, DCK, BodyLoc).build();
+    StmtResult Body = BuildBody(BodyLoc);
     if (!Body.isInvalid())
       ExceptSpec.CalledStmt(Body.get());
 
@@ -9499,6 +9509,20 @@ ComputeDefaultedComparisonExceptionSpec(Sema &S, SourceLocation Loc,
   return ExceptSpec;
 }
 
+static Sema::ImplicitExceptionSpecification
+ComputeDefaultedComparisonExceptionSpec(Sema &S, SourceLocation Loc,
+                                        FunctionDecl *FD,
+                                        DefaultedComparisonKind DCK) {
+  return computeExceptionSpecFromSynthesizedBody(
+      S, Loc, FD, [&](SourceLocation BodyLoc) {
+        CXXRecordDecl *RD =
+            cast<CXXRecordDecl>(FD->getFriendObjectKind() == Decl::FOK_None
+                                    ? FD->getDeclContext()
+                                    : FD->getLexicalDeclContext());
+        return DefaultedComparisonSynthesizer(S, RD, FD, DCK, BodyLoc).build();
+      });
+}
+
 //===----------------------------------------------------------------------===//
 // C++2d [over.inc.default]: defaulted postfix increment and decrement operator
 // functions (P3668R4).
@@ -10060,70 +10084,20 @@ bool Sema::CheckExplicitlyDefaultedPostfixOperator(Scope *S, FunctionDecl *FD,
 void Sema::DefineDefaultedPostfixOperator(SourceLocation UseLoc,
                                           FunctionDecl *FD,
                                           PostfixOperatorKind Kind) {
-  assert(FD->isDefaulted() && !FD->isDeleted() &&
-         !FD->doesThisDeclarationHaveABody());
-  if (FD->willHaveBody() || FD->isInvalidDecl())
-    return;
-
-  SynthesizedFunctionScope Scope(*this, FD);
-
-  // Add a context note for diagnostics produced after this point.
-  Scope.addContextNote(UseLoc);
-
-  DefaultedFunctionFPFeaturesRAII RestoreFP(*this, FD);
-
-  {
-    // Build and set up the function body.
-    SourceLocation BodyLoc =
-        FD->getEndLoc().isValid() ? FD->getEndLoc() : FD->getLocation();
-    StmtResult Body =
-        buildDefaultedPostfixOperatorBody(*this, FD, Kind, BodyLoc);
-    if (Body.isInvalid()) {
-      FD->setInvalidDecl();
-      return;
-    }
-    FD->setBody(Body.get());
-    FD->markUsed(Context);
-  }
-
-  // The exception specification is needed because we are defining the
-  // function. Note that this will reuse the body we just built.
-  ResolveExceptionSpec(UseLoc, FD->getType()->castAs<FunctionProtoType>());
-
-  if (ASTMutationListener *L = getASTMutationListener())
-    L->CompletedImplicitDefinition(FD);
+  defineDefaultedFunctionWithSynthesizedBody(
+      *this, UseLoc, FD, [&](SourceLocation BodyLoc) {
+        return buildDefaultedPostfixOperatorBody(*this, FD, Kind, BodyLoc);
+      });
 }
 
 static Sema::ImplicitExceptionSpecification
 ComputeDefaultedPostfixOperatorExceptionSpec(Sema &S, SourceLocation Loc,
                                              FunctionDecl *FD,
                                              PostfixOperatorKind Kind) {
-  ComputingExceptionSpec CES(S, FD, Loc);
-  Sema::ImplicitExceptionSpecification ExceptSpec(S);
-
-  if (FD->isInvalidDecl())
-    return ExceptSpec;
-
-  // The common case is that we just defined the function. In that case, just
-  // look at whether the body can throw.
-  if (Stmt *FunctionBody = FD->getBody()) {
-    ExceptSpec.CalledStmt(FunctionBody);
-  } else {
-    // Otherwise, build a body so we can check it. Pretend to synthesize the
-    // function body in an unevaluated context: we are not permitted to mark
-    // its callees as referenced.
-    Sema::SynthesizedFunctionScope Scope(S, FD);
-    EnterExpressionEvaluationContext Context(
-        S, Sema::ExpressionEvaluationContext::Unevaluated);
-
-    SourceLocation BodyLoc =
-        FD->getEndLoc().isValid() ? FD->getEndLoc() : FD->getLocation();
-    StmtResult Body = buildDefaultedPostfixOperatorBody(S, FD, Kind, BodyLoc);
-    if (!Body.isInvalid())
-      ExceptSpec.CalledStmt(Body.get());
-  }
-
-  return ExceptSpec;
+  return computeExceptionSpecFromSynthesizedBody(
+      S, Loc, FD, [&](SourceLocation BodyLoc) {
+        return buildDefaultedPostfixOperatorBody(S, FD, Kind, BodyLoc);
+      });
 }
 
 void Sema::CheckDelayedMemberExceptionSpecs() {

>From 9ceae7c9d5efa73793e2a3b9c645e203a471c33f Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Thu, 8 Oct 2026 06:45:20 +0300
Subject: [PATCH 17/35] Merge `err_defaulted_comparison_template` and
 `err_defaulted_postfix_operator_template`

---
 clang/include/clang/Basic/DiagnosticSemaKinds.td |  8 +++-----
 clang/lib/Sema/SemaDeclCXX.cpp                   | 10 +++-------
 2 files changed, 6 insertions(+), 12 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 4d06e8cd763eba..67b6051163c534 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10577,8 +10577,9 @@ def note_vbase_moved_here : Note<
 def select_defaulted_comparison_kind : TextSubstitution<
   "%select{<ERROR>|equality|three-way|equality|relational}0 comparison "
   "operator">;
-def err_defaulted_comparison_template : Error<
-  "comparison operator template cannot be defaulted">;
+def err_defaulted_operator_template : Error<
+  "%select{comparison operator|%sub{select_defaulted_postfix_operator_kind}1}0 "
+  "template cannot be defaulted">;
 def err_defaulted_comparison_num_args : Error<
   "%select{non-member|member}0 %sub{select_defaulted_comparison_kind}1"
   " must have %select{2|1}0 parameters">;
@@ -10683,9 +10684,6 @@ def select_defaulted_postfix_operator_kind : TextSubstitution<
   "%select{<ERROR>|postfix increment|postfix decrement}0 operator">;
 def err_defaulted_prefix_operator : Error<
   "only the postfix form of 'operator%select{++|--}0' can be defaulted">;
-def err_defaulted_postfix_operator_template : Error<
-  "%sub{select_defaulted_postfix_operator_kind}0 template cannot be "
-  "defaulted">;
 def err_defaulted_postfix_operator_const : Error<
   "defaulted member %sub{select_defaulted_postfix_operator_kind}0 cannot be "
   "const-qualified">;
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index c5844d3fdfd9a6..6341d2690ca917 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -19591,13 +19591,9 @@ void Sema::SetDeclDefaulted(Decl *Dcl, SourceLocation DefaultLoc, Scope *S) {
     if (auto *FTD = dyn_cast<FunctionTemplateDecl>(Dcl)) {
       FunctionDecl::DefaultedFunctionKind DFK =
           FTD->getTemplatedDecl()->getDefaultedFunctionKind();
-      if (DFK.isComparison()) {
-        Diag(DefaultLoc, diag::err_defaulted_comparison_template);
-        return;
-      }
-      if (DFK.isPostfixOperator()) {
-        Diag(DefaultLoc, diag::err_defaulted_postfix_operator_template)
-            << (int)DFK.asPostfixOperator();
+      if (DFK.isComparison() || DFK.isPostfixOperator()) {
+        Diag(DefaultLoc, diag::err_defaulted_operator_template)
+            << DFK.isPostfixOperator() << (int)DFK.asPostfixOperator();
         return;
       }
     }

>From 5bd54fd9b1747929713c9e57dca07e0578eded1f Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Thu, 8 Oct 2026 06:51:21 +0300
Subject: [PATCH 18/35] Add various defaulted postfix operator overloads with
 restrictions and error checks

Introduce new structures demonstrating defaulted postfix increment and decrement operators with various qualifiers such as `__restrict__`, `volatile`, and `const`. Include error checks for invalid return types and parameter types in defaulted operators. Enhance the handling of placeholder types and clarify the rules around defaulted operators in the context of C++ concepts and templates.
---
 .../over.inc/over.inc.default/p1.cpp          | 157 ++++++++++++++++++
 1 file changed, 157 insertions(+)

diff --git a/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p1.cpp b/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p1.cpp
index c970eb122da502..f7d78556883b11 100644
--- a/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p1.cpp
+++ b/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p1.cpp
@@ -39,6 +39,34 @@ struct RefQualified {
   RefQualified operator++(int) && = default; // OK, an rvalue reference is a "reference to C"
 };
 
+// __restrict__ is not part of the type of the implicit object parameter.
+struct Restrict {
+  Restrict &operator++();
+  Restrict operator++(int) __restrict__ = default; // OK
+};
+struct RestrictBoth {
+  RestrictBoth &operator--() __restrict__;
+  RestrictBoth operator--(int) __restrict__ = default; // OK
+};
+struct RestrictRvalue {
+  RestrictRvalue &operator++();
+  RestrictRvalue operator++(int) __restrict__ && = default; // OK
+};
+struct RestrictVolatile {
+  RestrictVolatile &operator++() volatile;
+  RestrictVolatile(const volatile RestrictVolatile &);
+  RestrictVolatile(RestrictVolatile &&);
+  RestrictVolatile operator++(int) volatile __restrict__ = default; // OK
+};
+struct RestrictConst {
+  RestrictConst &operator++();
+  RestrictConst operator++(int) const __restrict__ = default; // expected-error {{defaulted member postfix increment operator cannot be const-qualified}}
+};
+struct ExplicitRestrict {
+  ExplicitRestrict &operator++();
+  ExplicitRestrict operator++(this ExplicitRestrict &__restrict__, int) = default; // OK
+};
+
 // Explicit object parameters.
 struct Explicit {
   Explicit &operator++();
@@ -91,6 +119,33 @@ struct ReturnsAlias {
   Self &operator++();
   Self operator++(int) = default; // OK
 };
+struct ReturnsTrailing {
+  ReturnsTrailing &operator++();
+  auto operator++(int) -> ReturnsTrailing = default; // OK, not a deduced return type
+};
+
+// A placeholder type is not C, even if deduction would produce C.
+template <typename T> concept Any = true;
+struct ReturnsConstrainedAuto {
+  ReturnsConstrainedAuto &operator++();
+  Any auto operator++(int) = default; // expected-error {{defaulted postfix increment operator cannot have a deduced return type}}
+};
+struct ReturnsAutoPointer {
+  ReturnsAutoPointer &operator++();
+  auto *operator++(int) = default; // expected-error {{defaulted postfix increment operator cannot have a deduced return type}}
+};
+struct ReturnsAutoRef {
+  ReturnsAutoRef &operator--();
+  auto &operator--(int) = default; // expected-error {{defaulted postfix decrement operator cannot have a deduced return type}}
+};
+struct ReturnsConstAuto {
+  ReturnsConstAuto &operator++();
+  const auto operator++(int) = default; // expected-error {{defaulted postfix increment operator cannot have a deduced return type}}
+};
+struct ReturnsConstrainedAutoRef {
+  ReturnsConstrainedAutoRef &operator++();
+  const Any auto &operator++(int) = default; // expected-error {{defaulted postfix increment operator cannot have a deduced return type}}
+};
 
 // Non-member functions.
 struct N {
@@ -117,6 +172,16 @@ struct NMismatch { NMismatch &operator++(); };
 S operator++(NMismatch &, int) = default; // expected-error {{return type for defaulted postfix increment operator must be 'NMismatch', not 'S'}}
 struct NUnknown { NUnknown &operator++(); };
 S &operator++(NUnknown, int) = default; // expected-error {{invalid first parameter type for defaulted non-member postfix increment operator; found 'NUnknown', expected reference to a non-const class or enumeration type}}
+struct NAuto {
+  NAuto &operator++();
+  NAuto &operator--();
+};
+auto operator++(NAuto &, int) = default;           // expected-error {{defaulted postfix increment operator cannot have a deduced return type}}
+Any auto operator--(NAuto &, int) = default;       // expected-error {{defaulted postfix decrement operator cannot have a deduced return type}}
+decltype(auto) operator++(NAuto &&, int) = default; // expected-error {{defaulted postfix increment operator cannot have a deduced return type}}
+auto operator--(NAuto &&, int) -> NAuto = default;  // OK
+struct NRestrict { NRestrict &operator++(); };
+NRestrict operator++(NRestrict &__restrict__, int) = default; // OK
 
 // Enumerations.
 enum E { e };
@@ -171,6 +236,44 @@ struct Template {
 };
 template <typename T = void> S operator--(S &, int) = default; // expected-error {{postfix decrement operator template cannot be defaulted}}
 
+// A parameter declared with a placeholder type makes the function an
+// abbreviated function template.
+struct AutoFirst {
+  AutoFirst &operator++();
+  AutoFirst &operator--();
+  AutoFirst operator++(this auto &, int) = default;   // expected-error {{postfix increment operator template cannot be defaulted}}
+  friend AutoFirst operator--(auto &, int) = default; // expected-error {{postfix decrement operator template cannot be defaulted}}
+};
+AutoFirst operator++(Any auto &, int) = default; // expected-error {{postfix increment operator template cannot be defaulted}}
+AutoFirst operator--(auto &&, int) = default;    // expected-error {{postfix decrement operator template cannot be defaulted}}
+
+// The second parameter of a postfix operator always has type int.
+struct AutoSecond {
+  AutoSecond &operator++();
+  AutoSecond &operator--();
+  AutoSecond operator++(auto) = default;                    // expected-error {{postfix increment operator template cannot be defaulted}}
+  AutoSecond operator--(this AutoSecond &, auto) = default; // expected-error {{postfix decrement operator template cannot be defaulted}}
+};
+AutoSecond operator++(AutoSecond &&, Any auto) = default; // expected-error {{postfix increment operator template cannot be defaulted}}
+struct NotInt {
+  NotInt &operator++();
+  NotInt &operator--();
+  NotInt operator++(long) = default;                    // expected-error {{parameter of overloaded post-increment operator must have type 'int' (not 'long')}}
+  NotInt operator--(this NotInt &, unsigned) = default; // expected-error {{parameter of overloaded post-decrement operator must have type 'int' (not 'unsigned int')}}
+};
+NotInt operator++(NotInt &&, char) = default; // expected-error {{parameter of overloaded post-increment operator must have type 'int' (not 'char')}}
+struct IntSpellings {
+  using Int = int;
+  IntSpellings &operator++();
+  IntSpellings &operator--();
+  IntSpellings operator++(const int) = default; // OK
+  IntSpellings operator--(Int) = default;       // OK
+};
+struct DefaultArgument {
+  DefaultArgument &operator++();
+  DefaultArgument operator++(int = 0) = default; // expected-error {{parameter of overloaded 'operator++' cannot have a default argument}}
+};
+
 // Only the postfix forms can be defaulted.
 struct Prefix {
   Prefix &operator++() = default; // expected-error {{only the postfix form of 'operator++' can be defaulted}}
@@ -210,3 +313,57 @@ template <typename T> struct DependentAuto {
   friend auto operator++(T &, int) = default; // expected-error {{defaulted postfix increment operator cannot have a deduced return type}}
   auto operator--(this T &, int) = default;   // expected-error {{defaulted postfix decrement operator cannot have a deduced return type}}
 };
+
+template <typename T> struct DependentDeducedReturn {
+  DependentDeducedReturn &operator++();
+  DependentDeducedReturn &operator--();
+  auto operator++(int) = default;     // expected-error {{defaulted postfix increment operator cannot have a deduced return type}}
+  Any auto operator--(int) = default; // expected-error {{defaulted postfix decrement operator cannot have a deduced return type}}
+};
+template <typename T> struct DependentDeducedReturn2 {
+  DependentDeducedReturn2 &operator++();
+  DependentDeducedReturn2 &operator--();
+  decltype(auto) operator++(int) = default;                // expected-error {{defaulted postfix increment operator cannot have a deduced return type}}
+  friend T *operator--(DependentDeducedReturn2 &, int) = default; // OK, checked when instantiated
+};
+template <typename T> struct DependentConstrainedFriend {
+  DependentConstrainedFriend &operator++();
+  friend Any auto operator++(DependentConstrainedFriend &, int) = default; // expected-error {{defaulted postfix increment operator cannot have a deduced return type}}
+};
+
+// A trailing return type that names C is not deduced.
+template <typename T> struct DependentTrailing {
+  DependentTrailing &operator++();
+  auto operator++(int) -> T = default; // expected-error {{return type for defaulted postfix increment operator must be 'DependentTrailing<int>', not 'int'}}
+};
+DependentTrailing<int> dt1; // expected-note {{in instantiation of template class 'DependentTrailing<int>' requested here}}
+template <typename T> struct DependentTrailingOK {
+  DependentTrailingOK &operator++();
+  auto operator++(int) -> DependentTrailingOK = default; // OK
+};
+DependentTrailingOK<int> dt2;
+
+// A placeholder parameter makes the function a template even in a class
+// template.
+template <typename T> struct DependentAutoParam {
+  DependentAutoParam &operator++();
+  DependentAutoParam &operator--();
+  DependentAutoParam operator++(this auto &, int) = default;  // expected-error {{postfix increment operator template cannot be defaulted}}
+  friend DependentAutoParam operator--(auto &, int) = default; // expected-error {{postfix decrement operator template cannot be defaulted}}
+  DependentAutoParam operator++(auto) = default;               // expected-error {{postfix increment operator template cannot be defaulted}}
+};
+
+// A dependent second parameter is checked when instantiated.
+template <typename T> struct DependentSecond {
+  DependentSecond &operator++();
+  DependentSecond operator++(T) = default; // expected-error {{parameter of overloaded post-increment operator must have type 'int' (not 'long')}}
+};
+DependentSecond<int> ds1;  // OK
+DependentSecond<long> ds2; // expected-note {{in instantiation of template class 'DependentSecond<long>' requested here}}
+
+template <typename T> struct DependentRestrict {
+  DependentRestrict &operator++();
+  DependentRestrict operator++(int) __restrict__ = default;       // OK
+  DependentRestrict operator--(int) const __restrict__ = default; // expected-error {{defaulted member postfix decrement operator cannot be const-qualified}}
+};
+DependentRestrict<int> dres1; // expected-note {{in instantiation of template class 'DependentRestrict<int>' requested here}}

>From 44cc46ff5c39ab416ecbad5cbc8db96e27d44c95 Mon Sep 17 00:00:00 2001
From: Muhammad Bassiouni <60100307+bassiounix at users.noreply.github.com>
Date: Thu, 8 Oct 2026 17:03:29 +0300
Subject: [PATCH 19/35] Update clang/include/clang/Basic/DiagnosticSemaKinds.td

Co-authored-by: Aaron Ballman <aaron at aaronballman.com>
---
 clang/include/clang/Basic/DiagnosticSemaKinds.td | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 67b6051163c534..bc3435111538f1 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10681,7 +10681,7 @@ def err_ref_qualifier_comparison_operator : Error<
 // C++2d defaulted postfix increment and decrement operators
 // This corresponds to values of PostfixOperatorKind.
 def select_defaulted_postfix_operator_kind : TextSubstitution<
-  "%select{<ERROR>|postfix increment|postfix decrement}0 operator">;
+  "postfix %select{<ERROR>|increment|decrement}0 operator">;
 def err_defaulted_prefix_operator : Error<
   "only the postfix form of 'operator%select{++|--}0' can be defaulted">;
 def err_defaulted_postfix_operator_const : Error<

>From 5786b8fee71bb8465d956a3e0bd429f2137faad8 Mon Sep 17 00:00:00 2001
From: Muhammad Bassiouni <60100307+bassiounix at users.noreply.github.com>
Date: Thu, 8 Oct 2026 17:03:49 +0300
Subject: [PATCH 20/35] Update clang/lib/Sema/SemaDeclCXX.cpp

Co-authored-by: Aaron Ballman <aaron at aaronballman.com>
---
 clang/lib/Sema/SemaDeclCXX.cpp | 2 --
 1 file changed, 2 deletions(-)

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 6341d2690ca917..540106c5475621 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -9523,10 +9523,8 @@ ComputeDefaultedComparisonExceptionSpec(Sema &S, SourceLocation Loc,
       });
 }
 
-//===----------------------------------------------------------------------===//
 // C++2d [over.inc.default]: defaulted postfix increment and decrement operator
 // functions (P3668R4).
-//===----------------------------------------------------------------------===//
 
 namespace {
 /// Which diagnostics to produce while analyzing a defaulted postfix increment

>From 6b6ca225b5b82aa966dbcfebdf1a9ed8abbf458b Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Fri, 9 Oct 2026 02:04:43 +0300
Subject: [PATCH 21/35] extract lambda to local var

---
 clang/lib/Sema/SemaDeclCXX.cpp | 33 ++++++++++++++++-----------------
 1 file changed, 16 insertions(+), 17 deletions(-)

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 540106c5475621..fb270ad01a05bc 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -9453,15 +9453,14 @@ static void defineDefaultedFunctionWithSynthesizedBody(
 
 void Sema::DefineDefaultedComparison(SourceLocation UseLoc, FunctionDecl *FD,
                                      DefaultedComparisonKind DCK) {
-  defineDefaultedFunctionWithSynthesizedBody(
-      *this, UseLoc, FD, [&](SourceLocation BodyLoc) {
-        // The first parameter has type maybe-ref-to maybe-const T, use that to
-        // get the type of the class being compared.
-        auto PT = FD->getParamDecl(0)->getType();
-        CXXRecordDecl *RD = PT.getNonReferenceType()->getAsCXXRecordDecl();
-        return DefaultedComparisonSynthesizer(*this, RD, FD, DCK, BodyLoc)
-            .build();
-      });
+  auto BuildBody = [&](SourceLocation BodyLoc) {
+    // The first parameter has type maybe-ref-to maybe-const T, use that to
+    // get the type of the class being compared.
+    auto PT = FD->getParamDecl(0)->getType();
+    CXXRecordDecl *RD = PT.getNonReferenceType()->getAsCXXRecordDecl();
+    return DefaultedComparisonSynthesizer(*this, RD, FD, DCK, BodyLoc).build();
+  };
+  defineDefaultedFunctionWithSynthesizedBody(*this, UseLoc, FD, BuildBody);
 }
 
 /// Compute the exception specification of a defaulted function whose body is
@@ -9513,14 +9512,14 @@ static Sema::ImplicitExceptionSpecification
 ComputeDefaultedComparisonExceptionSpec(Sema &S, SourceLocation Loc,
                                         FunctionDecl *FD,
                                         DefaultedComparisonKind DCK) {
-  return computeExceptionSpecFromSynthesizedBody(
-      S, Loc, FD, [&](SourceLocation BodyLoc) {
-        CXXRecordDecl *RD =
-            cast<CXXRecordDecl>(FD->getFriendObjectKind() == Decl::FOK_None
-                                    ? FD->getDeclContext()
-                                    : FD->getLexicalDeclContext());
-        return DefaultedComparisonSynthesizer(S, RD, FD, DCK, BodyLoc).build();
-      });
+  auto BuildBody = [&](SourceLocation BodyLoc) {
+    CXXRecordDecl *RD =
+        cast<CXXRecordDecl>(FD->getFriendObjectKind() == Decl::FOK_None
+                                ? FD->getDeclContext()
+                                : FD->getLexicalDeclContext());
+    return DefaultedComparisonSynthesizer(S, RD, FD, DCK, BodyLoc).build();
+  };
+  return computeExceptionSpecFromSynthesizedBody(S, Loc, FD, BuildBody);
 }
 
 // C++2d [over.inc.default]: defaulted postfix increment and decrement operator

>From 149333aee85fe1d684f6be8b0f586197cb0854b9 Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Wed, 14 Oct 2026 05:07:20 +0300
Subject: [PATCH 22/35] use `getNumParamsIncludingThis`

---
 clang/lib/AST/Decl.cpp | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 71dffd8d78a013..e7470d8b63be37 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -3318,11 +3318,7 @@ FunctionDecl::getDefaultedFunctionKind() const {
     // Only the postfix forms can be defaulted. They are distinguished from the
     // prefix forms by their trailing 'int' parameter, so they have exactly two
     // parameters including implicit or explicit 'this'.
-    unsigned NumParams = getNumParams();
-    if (const auto *MD = dyn_cast<CXXMethodDecl>(this);
-        MD && MD->isImplicitObjectMemberFunction())
-      ++NumParams;
-    if (NumParams != 2)
+    if (getNumParamsIncludingThis() != 2)
       break;
     return OO == OO_PlusPlus ? PostfixOperatorKind::Increment
                              : PostfixOperatorKind::Decrement;

>From 182e2730967764b542ca010128cab81bf417a385 Mon Sep 17 00:00:00 2001
From: Muhammad Bassiouni <60100307+bassiounix at users.noreply.github.com>
Date: Wed, 14 Oct 2026 05:13:45 +0300
Subject: [PATCH 23/35] Update clang/docs/ReleaseNotes.md

Co-authored-by: Corentin Jabot <corentinjabot at gmail.com>
---
 clang/docs/ReleaseNotes.md | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 40236a86d4a993..e6ddf1624a12cd 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -186,9 +186,8 @@ features cannot lower the translation-unit ABI level;
 
 - Clang now supports [P3668R4](https://wg21.link/p3668r4) (Defaulting postfix
   increment and decrement operations), which allows the postfix `operator++(int)`
-  and `operator--(int)` to be explicitly defaulted. The implicit definition
-  copies the object, applies the corresponding prefix operator, and returns the
-  copy. This is available in all C++ language modes as an extension, controlled
+  and `operator--(int)` to be explicitly defaulted. 
+  This is available in all C++ language modes as an extension, controlled
   by `-Wc++2d-extensions` and `-Wpre-c++2d-compat`.
 
 #### C++2c Feature Support

>From a5b0665665e3617d3fbe9c48d6007c5844805f54 Mon Sep 17 00:00:00 2001
From: Muhammad Bassiouni <60100307+bassiounix at users.noreply.github.com>
Date: Wed, 14 Oct 2026 05:14:10 +0300
Subject: [PATCH 24/35] Update clang/include/clang/AST/Decl.h

Co-authored-by: Corentin Jabot <corentinjabot at gmail.com>
---
 clang/include/clang/AST/Decl.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index f2a9420185cfdc..7d550646ba68bc 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -2178,6 +2178,7 @@ class FunctionDecl : public DeclaratorDecl,
     DefaultedComparisonKind asComparison() const {
       return static_cast<DefaultedComparisonKind>(Comparison);
     }
+    
     PostfixOperatorKind asPostfixOperator() const {
       return static_cast<PostfixOperatorKind>(PostfixOperator);
     }

>From c3519ecfb480cb8157a1a66e2a479beb7cdc79e8 Mon Sep 17 00:00:00 2001
From: Muhammad Bassiouni <60100307+bassiounix at users.noreply.github.com>
Date: Wed, 14 Oct 2026 05:14:28 +0300
Subject: [PATCH 25/35] Update clang/include/clang/AST/Decl.h

Co-authored-by: Corentin Jabot <corentinjabot at gmail.com>
---
 clang/include/clang/AST/Decl.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index 7d550646ba68bc..1a0fac5f12bc5f 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -2151,6 +2151,7 @@ class FunctionDecl : public DeclaratorDecl,
         : DefaultedFunctionKind() {
       Comparison = llvm::to_underlying(Comp);
     }
+    
     DefaultedFunctionKind(PostfixOperatorKind PO) : DefaultedFunctionKind() {
       PostfixOperator = llvm::to_underlying(PO);
     }

>From ada68dd40bc3eb9e0433345cdb8b80c9377efbb6 Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Wed, 14 Oct 2026 05:19:36 +0300
Subject: [PATCH 26/35] format

---
 clang/include/clang/AST/Decl.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index 1a0fac5f12bc5f..cee0222aa4ba8f 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -2151,7 +2151,7 @@ class FunctionDecl : public DeclaratorDecl,
         : DefaultedFunctionKind() {
       Comparison = llvm::to_underlying(Comp);
     }
-    
+
     DefaultedFunctionKind(PostfixOperatorKind PO) : DefaultedFunctionKind() {
       PostfixOperator = llvm::to_underlying(PO);
     }
@@ -2179,7 +2179,7 @@ class FunctionDecl : public DeclaratorDecl,
     DefaultedComparisonKind asComparison() const {
       return static_cast<DefaultedComparisonKind>(Comparison);
     }
-    
+
     PostfixOperatorKind asPostfixOperator() const {
       return static_cast<PostfixOperatorKind>(PostfixOperator);
     }

>From 5cdbf4d5a4be53db09c7e2614a7deef9cebe3a60 Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Wed, 14 Oct 2026 05:32:13 +0300
Subject: [PATCH 27/35] Simplify the first parameter diagnostic for defaulted
 postfix operators

Always say "expected 'C &'" instead of also listing the deprecated
'volatile C &' alternative, and drop the separate "unknown type"
diagnostic for non-members: CheckOverloadedOperatorDeclaration already
rejects operators without a class or enumeration parameter, including
friends instantiated from class templates.

Co-Authored-By: Claude Fable 5.1 <noreply at anthropic.com>
---
 .../clang/Basic/DiagnosticSemaKinds.td        |  7 +-
 clang/lib/Sema/SemaDeclCXX.cpp                | 68 +++++++------------
 .../over.inc/over.inc.default/p1.cpp          | 17 ++---
 3 files changed, 36 insertions(+), 56 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index a0fc714c52a776..415ff3db96b8c9 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10707,12 +10707,7 @@ def err_defaulted_postfix_operator_const : Error<
   "const-qualified">;
 def err_defaulted_postfix_operator_param : Error<
   "invalid %select{explicit object|first}1 parameter type for defaulted "
-  "%sub{select_defaulted_postfix_operator_kind}0; found %2, expected %3 "
-  "or %4">;
-def err_defaulted_postfix_operator_param_unknown : Error<
-  "invalid first parameter type for defaulted non-member "
-  "%sub{select_defaulted_postfix_operator_kind}0; found %1, expected "
-  "reference to a non-const class or enumeration type">;
+  "%sub{select_defaulted_postfix_operator_kind}0; found %2, expected %3">;
 def err_defaulted_postfix_operator_return_type : Error<
   "return type for defaulted %sub{select_defaulted_postfix_operator_kind}0 "
   "must be %1, not %2">;
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index a3dd907bd4ea81..6b632c43bd7875 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -9878,54 +9878,38 @@ bool Sema::CheckExplicitlyDefaultedPostfixOperator(Scope *S, FunctionDecl *FD,
   QualType FirstParamType = getPostfixOperatorFirstParamType(FD);
   QualType ReturnType = FD->getDeclaredReturnType();
 
-  // Diagnose an invalid first parameter. ExpectedC is the type that the
-  // parameter should be a reference to, if that type is known.
-  auto DiagnoseFirstParam = [&](QualType ExpectedC) {
-    if (MD && MD->isImplicitObjectMemberFunction()) {
-      // The implicit object parameter is always a reference to the enclosing
-      // class, so the only possible problem is a const qualifier.
-      Diag(MD->getLocation(), diag::err_defaulted_postfix_operator_const)
-          << (int)Kind;
-    } else if (ExpectedC.isNull()) {
-      Diag(FirstParam->getBeginLoc(),
-           diag::err_defaulted_postfix_operator_param_unknown)
-          << (int)Kind << FirstParamType << FirstParam->getSourceRange();
-    } else {
-      Diag(FirstParam->getBeginLoc(),
-           diag::err_defaulted_postfix_operator_param)
-          << (int)Kind << !MD << FirstParamType
-          << Context.getLValueReferenceType(ExpectedC)
-          << Context.getLValueReferenceType(ExpectedC.withVolatile())
-          << FirstParam->getSourceRange();
-    }
-    return true;
-  };
-
   // Check the type of the first parameter and determine the type C from it.
   // If the parameter type is dependent, C is not known until instantiation.
   QualType C;
   if (!FirstParamType->isDependentType()) {
-    QualType EnclosingClass =
-        MD ? Context.getCanonicalTagType(MD->getParent()) : QualType();
-
-    // The first parameter must be a reference to C.
-    if (!FirstParamType->isReferenceType())
-      return DiagnoseFirstParam(EnclosingClass);
-    QualType Referent = FirstParamType.getNonReferenceType();
-    C = Referent.getUnqualifiedType();
-
     // For a member function, C is the enclosing class. For a non-member
-    // function, C must be a class or enumeration type.
-    if (MD) {
-      if (!Context.hasSameType(C, EnclosingClass))
-        return DiagnoseFirstParam(EnclosingClass);
-    } else if (!C->isRecordType() && !C->isEnumeralType()) {
-      return DiagnoseFirstParam(QualType());
+    // function, C is the class or enumeration type of its first parameter;
+    // CheckOverloadedOperatorDeclaration has already required it to have one.
+    QualType Referent = FirstParamType.getNonReferenceType();
+    C = MD ? Context.getCanonicalTagType(MD->getParent())
+           : Referent.getUnqualifiedType();
+    assert((C->isRecordType() || C->isEnumeralType()) &&
+           "postfix operator for a non-class, non-enumeration type");
+
+    // The first parameter must be a reference to C, which may be
+    // volatile-qualified but not const-qualified.
+    if (!FirstParamType->isReferenceType() ||
+        !Context.hasSameUnqualifiedType(Referent, C) ||
+        Referent.isConstQualified()) {
+      if (MD && MD->isImplicitObjectMemberFunction()) {
+        // The implicit object parameter is always a reference to the enclosing
+        // class, so the only possible problem is a const qualifier.
+        Diag(MD->getLocation(), diag::err_defaulted_postfix_operator_const)
+            << (int)Kind;
+      } else {
+        Diag(FirstParam->getBeginLoc(),
+             diag::err_defaulted_postfix_operator_param)
+            << (int)Kind << !MD << FirstParamType
+            << Context.getLValueReferenceType(C)
+            << FirstParam->getSourceRange();
+      }
+      return true;
     }
-
-    // The referent may be volatile-qualified, but not const-qualified.
-    if (Referent.isConstQualified())
-      return DiagnoseFirstParam(C);
   }
 
   // A deduced return type is never permitted, so diagnose it even if C is not
diff --git a/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p1.cpp b/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p1.cpp
index f7d78556883b11..74f5164b2fa44e 100644
--- a/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p1.cpp
+++ b/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p1.cpp
@@ -82,15 +82,15 @@ struct ExplicitRvalue {
 };
 struct ExplicitConst {
   ExplicitConst &operator++();
-  ExplicitConst operator++(this const ExplicitConst &, int) = default; // expected-error {{invalid explicit object parameter type for defaulted postfix increment operator; found 'const ExplicitConst &', expected 'ExplicitConst &' or 'volatile ExplicitConst &'}}
+  ExplicitConst operator++(this const ExplicitConst &, int) = default; // expected-error {{invalid explicit object parameter type for defaulted postfix increment operator; found 'const ExplicitConst &', expected 'ExplicitConst &'}}
 };
 struct ExplicitOther {
   ExplicitOther &operator++();
-  ExplicitOther operator++(this S &, int) = default; // expected-error {{invalid explicit object parameter type for defaulted postfix increment operator; found 'S &', expected 'ExplicitOther &' or 'volatile ExplicitOther &'}}
+  ExplicitOther operator++(this S &, int) = default; // expected-error {{invalid explicit object parameter type for defaulted postfix increment operator; found 'S &', expected 'ExplicitOther &'}}
 };
 struct ExplicitByValue {
   ExplicitByValue &operator--();
-  ExplicitByValue operator--(this ExplicitByValue, int) = default; // expected-error {{invalid explicit object parameter type for defaulted postfix decrement operator; found 'ExplicitByValue', expected 'ExplicitByValue &' or 'volatile ExplicitByValue &'}}
+  ExplicitByValue operator--(this ExplicitByValue, int) = default; // expected-error {{invalid explicit object parameter type for defaulted postfix decrement operator; found 'ExplicitByValue', expected 'ExplicitByValue &'}}
 };
 
 // Return type.
@@ -163,15 +163,16 @@ struct NV {
 NV operator++(volatile NV &, int) = default; // OK
 
 struct NConst { NConst &operator++(); };
-NConst operator++(const NConst &, int) = default; // expected-error {{invalid first parameter type for defaulted postfix increment operator; found 'const NConst &', expected 'NConst &' or 'volatile NConst &'}}
+NConst operator++(const NConst &, int) = default; // expected-error {{invalid first parameter type for defaulted postfix increment operator; found 'const NConst &', expected 'NConst &'}}
 struct NValue { NValue &operator++(); };
-NValue operator++(NValue, int) = default; // expected-error {{invalid first parameter type for defaulted non-member postfix increment operator; found 'NValue', expected reference to a non-const class or enumeration type}}
+NValue operator++(NValue, int) = default; // expected-error {{invalid first parameter type for defaulted postfix increment operator; found 'NValue', expected 'NValue &'}}
 struct NRet { NRet &operator--(); };
 int operator--(NRet &, int) = default; // expected-error {{return type for defaulted postfix decrement operator must be 'NRet', not 'int'}}
 struct NMismatch { NMismatch &operator++(); };
 S operator++(NMismatch &, int) = default; // expected-error {{return type for defaulted postfix increment operator must be 'NMismatch', not 'S'}}
-struct NUnknown { NUnknown &operator++(); };
-S &operator++(NUnknown, int) = default; // expected-error {{invalid first parameter type for defaulted non-member postfix increment operator; found 'NUnknown', expected reference to a non-const class or enumeration type}}
+// The first parameter is checked before the return type.
+struct NValueBadReturn { NValueBadReturn &operator++(); };
+S &operator++(NValueBadReturn, int) = default; // expected-error {{invalid first parameter type for defaulted postfix increment operator; found 'NValueBadReturn', expected 'NValueBadReturn &'}}
 struct NAuto {
   NAuto &operator++();
   NAuto &operator--();
@@ -296,7 +297,7 @@ DependentReturn<int> dr1; // expected-note {{in instantiation of template class
 
 template <typename T> struct DependentObject {
   DependentObject &operator++();
-  DependentObject operator++(this T &, int) = default; // expected-error {{invalid explicit object parameter type for defaulted postfix increment operator; found 'int &', expected 'DependentObject<int> &' or 'volatile DependentObject<int> &'}}
+  DependentObject operator++(this T &, int) = default; // expected-error {{invalid explicit object parameter type for defaulted postfix increment operator; found 'int &', expected 'DependentObject<int> &'}}
 };
 DependentObject<int> do1; // expected-note {{in instantiation of template class 'DependentObject<int>' requested here}}
 

>From e60bca4bfdbe6883174780c3a6bf33682e41a639 Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Wed, 14 Oct 2026 05:34:01 +0300
Subject: [PATCH 28/35] Inline single-use getDefaultedPostfixOperatorType
 helper

Co-Authored-By: Claude Sonnet 5.5 <noreply at anthropic.com>
---
 clang/lib/Sema/SemaDeclCXX.cpp | 16 +++++-----------
 1 file changed, 5 insertions(+), 11 deletions(-)

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 6b632c43bd7875..b76a8d54771985 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -9553,14 +9553,6 @@ static QualType getPostfixOperatorFirstParamType(const FunctionDecl *FD) {
   return FD->getParamDecl(0)->getType();
 }
 
-/// Get the type C for which a well-formed defaulted postfix increment or
-/// decrement operator function is defaulted.
-static QualType getDefaultedPostfixOperatorType(const FunctionDecl *FD) {
-  return getPostfixOperatorFirstParamType(FD)
-      .getNonReferenceType()
-      .getUnqualifiedType();
-}
-
 /// Determine whether a defaulted postfix increment or decrement operator
 /// function for type \p C should be defined as deleted, as specified in C++2d
 /// [over.inc.default]p2, and whether every function it would invoke is
@@ -10761,9 +10753,11 @@ void Sema::DiagnoseDeletedDefaultedFunction(FunctionDecl *FD) {
     ShouldDeleteSpecialMember(cast<CXXMethodDecl>(FD), DFK.asSpecialMember(),
                               nullptr, /*Diagnose=*/true);
   } else if (DFK.isPostfixOperator()) {
-    analyzeDefaultedPostfixOperator(
-        *this, FD, DFK.asPostfixOperator(), getDefaultedPostfixOperatorType(FD),
-        PostfixOperatorDiagnoseKind::ExplainDeleted);
+    QualType C = getPostfixOperatorFirstParamType(FD)
+                     .getNonReferenceType()
+                     .getUnqualifiedType();
+    analyzeDefaultedPostfixOperator(*this, FD, DFK.asPostfixOperator(), C,
+                                    PostfixOperatorDiagnoseKind::ExplainDeleted);
   } else {
     DefaultedComparisonAnalyzer(
         *this, cast<CXXRecordDecl>(FD->getLexicalDeclContext()), FD,

>From 9fb65f5ffb468f1d70eb42c028ee6e64f5eab74b Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Wed, 14 Oct 2026 20:19:14 +0300
Subject: [PATCH 29/35] Analyze defaulted postfix operators with a single
 Diagnose flag

Replace the NoDiagnostics/ExplainDeleted/ExplainConstexpr mode with a
bool. Non-constexpr callees are collected during the analysis and only
noted once it is known that the function is not deleted.

Co-Authored-By: Claude Opus 5.5 <noreply at anthropic.com>
---
 clang/lib/Sema/SemaDeclCXX.cpp | 52 ++++++++++++++++------------------
 1 file changed, 24 insertions(+), 28 deletions(-)

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index b76a8d54771985..0caf8e6eae8a69 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -9526,14 +9526,6 @@ ComputeDefaultedComparisonExceptionSpec(Sema &S, SourceLocation Loc,
 // functions (P3668R4).
 
 namespace {
-/// Which diagnostics to produce while analyzing a defaulted postfix increment
-/// or decrement operator function.
-enum class PostfixOperatorDiagnoseKind {
-  NoDiagnostics,
-  ExplainDeleted,
-  ExplainConstexpr
-};
-
 /// Information about a defaulted postfix increment or decrement operator
 /// function, as determined by analyzeDefaultedPostfixOperator.
 struct DefaultedPostfixOperatorInfo {
@@ -9557,11 +9549,13 @@ static QualType getPostfixOperatorFirstParamType(const FunctionDecl *FD) {
 /// function for type \p C should be defined as deleted, as specified in C++2d
 /// [over.inc.default]p2, and whether every function it would invoke is
 /// constexpr.
+///
+/// If \p Diagnose is true, explain why the function is defined as deleted or,
+/// if it is not, which of the functions it invokes are not constexpr.
 static DefaultedPostfixOperatorInfo
 analyzeDefaultedPostfixOperator(Sema &S, FunctionDecl *FD,
                                 PostfixOperatorKind Kind, QualType C,
-                                PostfixOperatorDiagnoseKind Diagnose) {
-  using DiagnoseKind = PostfixOperatorDiagnoseKind;
+                                bool Diagnose) {
   DefaultedPostfixOperatorInfo Info;
   SourceLocation Loc = FD->getLocation();
 
@@ -9582,27 +9576,27 @@ analyzeDefaultedPostfixOperator(Sema &S, FunctionDecl *FD,
     return Info;
   };
   auto InaccessibleDiag = [&](unsigned Step) {
-    return Diagnose == DiagnoseKind::ExplainDeleted
+    return Diagnose
                ? S.PDiag(diag::note_defaulted_postfix_operator_inaccessible)
                      << FD << Step
                : S.PDiag();
   };
   auto NoteCallsDeleted = [&](unsigned Step, FunctionDecl *Callee) {
-    if (Diagnose != DiagnoseKind::ExplainDeleted)
+    if (!Diagnose)
       return;
     S.Diag(Loc, diag::note_defaulted_postfix_operator_calls_deleted)
         << FD << Step;
     if (Callee)
       S.NoteDeletedFunction(Callee);
   };
+  // The non-constexpr functions are only worth explaining if the function is
+  // not deleted, which isn't known until the end of the analysis.
+  SmallVector<std::pair<const FunctionDecl *, unsigned>, 2> NonConstexpr;
   auto CheckConstexpr = [&](const FunctionDecl *Callee, unsigned Step) {
     if (Callee->isConstexpr())
       return;
     Info.Constexpr = false;
-    if (Diagnose == DiagnoseKind::ExplainConstexpr)
-      S.Diag(Callee->getLocation(),
-             diag::note_defaulted_postfix_operator_not_constexpr_here)
-          << Step;
+    NonConstexpr.push_back({Callee, Step});
   };
 
   // C++2d [over.inc.default]p2:
@@ -9618,7 +9612,7 @@ analyzeDefaultedPostfixOperator(Sema &S, FunctionDecl *FD,
         InitializationKind::CreateDirect(Loc, Loc, Loc);
     InitializationSequence Seq(S, Entity, InitKind, Args);
     if (Seq.Failed()) {
-      if (Diagnose == DiagnoseKind::ExplainDeleted) {
+      if (Diagnose) {
         OverloadCandidateSet &Cands = Seq.getFailedCandidateSet();
         OverloadingResult OR =
             Seq.getFailureKind() ==
@@ -9715,7 +9709,7 @@ analyzeDefaultedPostfixOperator(Sema &S, FunctionDecl *FD,
     break;
 
   case OR_Ambiguous:
-    if (Diagnose == DiagnoseKind::ExplainDeleted)
+    if (Diagnose)
       CandidateSet.NoteCandidates(
           PartialDiagnosticAt(
               Loc, S.PDiag(diag::note_defaulted_postfix_operator_ambiguous)
@@ -9731,12 +9725,17 @@ analyzeDefaultedPostfixOperator(Sema &S, FunctionDecl *FD,
     // Don't list the non-viable candidates: like for any other unary operator,
     // the candidate notes can't sensibly describe an arity mismatch for a
     // member postfix operator (whose object argument is not a parameter).
-    if (Diagnose == DiagnoseKind::ExplainDeleted)
+    if (Diagnose)
       S.Diag(Loc, diag::note_defaulted_postfix_operator_no_viable)
           << FD << (int)Kind << C;
     return Deleted();
   }
 
+  if (Diagnose)
+    for (auto [Callee, Step] : NonConstexpr)
+      S.Diag(Callee->getLocation(),
+             diag::note_defaulted_postfix_operator_not_constexpr_here)
+          << Step;
   return Info;
 }
 
@@ -9938,8 +9937,8 @@ bool Sema::CheckExplicitlyDefaultedPostfixOperator(Scope *S, FunctionDecl *FD,
     return true;
 
   // Determine whether the function should be defined as deleted.
-  DefaultedPostfixOperatorInfo Info = analyzeDefaultedPostfixOperator(
-      *this, FD, Kind, C, PostfixOperatorDiagnoseKind::NoDiagnostics);
+  DefaultedPostfixOperatorInfo Info =
+      analyzeDefaultedPostfixOperator(*this, FD, Kind, C, /*Diagnose=*/false);
 
   bool First = FD == FD->getFirstDecl();
 
@@ -9954,8 +9953,7 @@ bool Sema::CheckExplicitlyDefaultedPostfixOperator(Scope *S, FunctionDecl *FD,
       Diag(FD->getLocation(),
            diag::err_non_first_default_postfix_operator_deletes)
           << (int)Kind;
-      analyzeDefaultedPostfixOperator(
-          *this, FD, Kind, C, PostfixOperatorDiagnoseKind::ExplainDeleted);
+      analyzeDefaultedPostfixOperator(*this, FD, Kind, C, /*Diagnose=*/true);
       return true;
     }
     if (isa<CXXRecordDecl>(FD->getLexicalDeclContext())) {
@@ -9979,8 +9977,7 @@ bool Sema::CheckExplicitlyDefaultedPostfixOperator(Scope *S, FunctionDecl *FD,
     if (!inTemplateInstantiation() && !FD->isImplicit()) {
       Diag(FD->getLocation(), diag::warn_defaulted_postfix_operator_deleted)
           << (int)Kind;
-      analyzeDefaultedPostfixOperator(
-          *this, FD, Kind, C, PostfixOperatorDiagnoseKind::ExplainDeleted);
+      analyzeDefaultedPostfixOperator(*this, FD, Kind, C, /*Diagnose=*/true);
       if (FD->getDefaultLoc().isValid())
         Diag(FD->getDefaultLoc(), diag::note_replace_equals_default_to_delete)
             << FixItHint::CreateReplacement(FD->getDefaultLoc(), "delete");
@@ -10006,8 +10003,7 @@ bool Sema::CheckExplicitlyDefaultedPostfixOperator(Scope *S, FunctionDecl *FD,
     Diag(FD->getBeginLoc(),
          diag::err_defaulted_postfix_operator_constexpr_mismatch)
         << (int)Kind << FD->isConsteval();
-    analyzeDefaultedPostfixOperator(
-        *this, FD, Kind, C, PostfixOperatorDiagnoseKind::ExplainConstexpr);
+    analyzeDefaultedPostfixOperator(*this, FD, Kind, C, /*Diagnose=*/true);
     return true;
   }
 
@@ -10757,7 +10753,7 @@ void Sema::DiagnoseDeletedDefaultedFunction(FunctionDecl *FD) {
                      .getNonReferenceType()
                      .getUnqualifiedType();
     analyzeDefaultedPostfixOperator(*this, FD, DFK.asPostfixOperator(), C,
-                                    PostfixOperatorDiagnoseKind::ExplainDeleted);
+                                    /*Diagnose=*/true);
   } else {
     DefaultedComparisonAnalyzer(
         *this, cast<CXXRecordDecl>(FD->getLexicalDeclContext()), FD,

>From 3d9d45f401fc8a14055d6040c1aed122a768c7f6 Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Thu, 15 Oct 2026 09:46:37 +0300
Subject: [PATCH 30/35] Share the calls-deleted note between defaulted
 comparisons and postfix operators

Co-Authored-By: Claude Opus 5.5 <noreply at anthropic.com>
---
 clang/include/clang/Basic/DiagnosticSemaKinds.td | 12 ++++++------
 clang/lib/Sema/SemaDeclCXX.cpp                   |  9 ++++-----
 2 files changed, 10 insertions(+), 11 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 415ff3db96b8c9..354ed53dced5ef 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10646,9 +10646,12 @@ def note_defaulted_comparison_inaccessible : Note<
   "defaulted %0 is implicitly deleted because it would invoke a "
   "%select{private|protected}3 %4%select{ member of %6|"
   " member of %6 to compare member %2| to compare base class %2}1">;
-def note_defaulted_comparison_calls_deleted : Note<
+// Shared by defaulted comparisons and defaulted postfix operators. The first
+// four %select indices of %1 are given by diag::DefaultedPostfixOperatorStep.
+def note_defaulted_function_calls_deleted : Note<
   "defaulted %0 is implicitly deleted because it would invoke a deleted "
-  "comparison function%select{| for member %2| for base class %2}1">;
+  "%select{constructor|destructor|prefix 'operator++'|prefix 'operator--'|"
+  "comparison function}1%select{| for member %3| for base class %3}2">;
 def note_defaulted_comparison_no_viable_function : Note<
   "defaulted %0 is implicitly deleted because there is no viable "
   "%select{three-way comparison function|'operator=='}1 for "
@@ -10734,11 +10737,8 @@ def note_defaulted_postfix_operator_ambiguous : Note<
   "defaulted %0 is implicitly deleted because %select{copy construction of|"
   "prefix 'operator++' for|prefix 'operator--' for}1 an lvalue of type %2 "
   "is ambiguous">;
-// The %select indices of the operations in the following notes are given by
+// The %select indices of the operations in the following note are given by
 // diag::DefaultedPostfixOperatorStep.
-def note_defaulted_postfix_operator_calls_deleted : Note<
-  "defaulted %0 is implicitly deleted because it would invoke a deleted "
-  "%select{constructor|destructor|prefix 'operator++'|prefix 'operator--'}1">;
 def note_defaulted_postfix_operator_inaccessible : Note<
   "defaulted %0 is implicitly deleted because it would invoke a "
   "%select{private|protected}2 %select{constructor|destructor|"
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 0caf8e6eae8a69..e013c34e7ef042 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -8601,9 +8601,8 @@ class DefaultedComparisonAnalyzer
                  diag::note_defaulted_comparison_not_rewritten_callee)
               << FD;
         } else {
-          S.Diag(Subobj.Loc,
-                 diag::note_defaulted_comparison_calls_deleted)
-              << FD << Subobj.Kind << Subobj.Decl;
+          S.Diag(Subobj.Loc, diag::note_defaulted_function_calls_deleted)
+              << FD << /*comparison function*/ 4 << Subobj.Kind << Subobj.Decl;
           S.NoteDeletedFunction(Best->Function);
         }
       }
@@ -9584,8 +9583,8 @@ analyzeDefaultedPostfixOperator(Sema &S, FunctionDecl *FD,
   auto NoteCallsDeleted = [&](unsigned Step, FunctionDecl *Callee) {
     if (!Diagnose)
       return;
-    S.Diag(Loc, diag::note_defaulted_postfix_operator_calls_deleted)
-        << FD << Step;
+    S.Diag(Loc, diag::note_defaulted_function_calls_deleted)
+        << FD << Step << /*complete object*/ 0;
     if (Callee)
       S.NoteDeletedFunction(Callee);
   };

>From 3d43e5316a49ee9963ad5c63936a04f1ddd9a555 Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Sun, 18 Oct 2026 07:36:47 +0300
Subject: [PATCH 31/35] Use InitializationSequence::Diagnose for defaulted
 postfix operator copy

Replace the hand-written explanation of why copy construction fails in a
defaulted postfix operator with InitializationSequence::Diagnose. This
emits errors rather than notes, so it is a FIXME for now.

Co-Authored-By: Claude Opus 5.5 <noreply at anthropic.com>
---
 .../clang/Basic/DiagnosticSemaKinds.td        | 10 ++---
 clang/lib/Sema/SemaDeclCXX.cpp                | 41 +++----------------
 .../over.inc/over.inc.default/p2.cpp          |  8 ++--
 3 files changed, 14 insertions(+), 45 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 354ed53dced5ef..a204e5b8eebe87 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10730,13 +10730,11 @@ def warn_defaulted_postfix_operator_deleted : Warning<
   "explicitly defaulted %sub{select_defaulted_postfix_operator_kind}0 is "
   "implicitly deleted">, InGroup<DefaultedFunctionDeleted>;
 def note_defaulted_postfix_operator_no_viable : Note<
-  "defaulted %0 is implicitly deleted because there is no viable "
-  "%select{constructor to copy|prefix 'operator++' for|prefix 'operator--' "
-  "for}1 an lvalue of type %2">;
+  "defaulted %0 is implicitly deleted because there is no viable prefix "
+  "%select{'operator++'|'operator--'}1 for an lvalue of type %2">;
 def note_defaulted_postfix_operator_ambiguous : Note<
-  "defaulted %0 is implicitly deleted because %select{copy construction of|"
-  "prefix 'operator++' for|prefix 'operator--' for}1 an lvalue of type %2 "
-  "is ambiguous">;
+  "defaulted %0 is implicitly deleted because prefix "
+  "%select{'operator++'|'operator--'}1 for an lvalue of type %2 is ambiguous">;
 // The %select indices of the operations in the following note are given by
 // diag::DefaultedPostfixOperatorStep.
 def note_defaulted_postfix_operator_inaccessible : Note<
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index e013c34e7ef042..b874660ccef743 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -9611,39 +9611,10 @@ analyzeDefaultedPostfixOperator(Sema &S, FunctionDecl *FD,
         InitializationKind::CreateDirect(Loc, Loc, Loc);
     InitializationSequence Seq(S, Entity, InitKind, Args);
     if (Seq.Failed()) {
-      if (Diagnose) {
-        OverloadCandidateSet &Cands = Seq.getFailedCandidateSet();
-        OverloadingResult OR =
-            Seq.getFailureKind() ==
-                    InitializationSequence::FK_ConstructorOverloadFailed
-                ? Seq.getFailedOverloadResult()
-                : OR_No_Viable_Function;
-        OverloadCandidateSet::iterator Best;
-        switch (OR) {
-        case OR_Deleted:
-          NoteCallsDeleted(diag::DefaultedPostfixOperatorStep::Constructor,
-                           Cands.BestViableFunction(S, Loc, Best) == OR_Deleted
-                               ? Best->Function
-                               : nullptr);
-          break;
-        case OR_Ambiguous:
-          Cands.NoteCandidates(
-              PartialDiagnosticAt(
-                  Loc, S.PDiag(diag::note_defaulted_postfix_operator_ambiguous)
-                           << FD << /*copy construction*/ 0 << C),
-              S, OCD_AmbiguousCandidates, Args, /*Opc=*/"", Loc);
-          break;
-        case OR_No_Viable_Function:
-          Cands.NoteCandidates(
-              PartialDiagnosticAt(
-                  Loc, S.PDiag(diag::note_defaulted_postfix_operator_no_viable)
-                           << FD << /*copy construction*/ 0 << C),
-              S, OCD_AllCandidates, Args, /*Opc=*/"", Loc);
-          break;
-        case OR_Success:
-          llvm_unreachable("initialization sequence did not fail");
-        }
-      }
+      // FIXME: This emits errors rather than notes explaining why the
+      // function is deleted.
+      if (Diagnose)
+        Seq.Diagnose(S, Entity, InitKind, Args);
       return Deleted();
     }
     for (const InitializationSequence::Step &Step : Seq.steps()) {
@@ -9712,7 +9683,7 @@ analyzeDefaultedPostfixOperator(Sema &S, FunctionDecl *FD,
       CandidateSet.NoteCandidates(
           PartialDiagnosticAt(
               Loc, S.PDiag(diag::note_defaulted_postfix_operator_ambiguous)
-                       << FD << (int)Kind << C),
+                       << FD << !IsIncrement << C),
           S, OCD_AmbiguousCandidates, Args, IsIncrement ? "++" : "--", Loc);
     return Deleted();
 
@@ -9726,7 +9697,7 @@ analyzeDefaultedPostfixOperator(Sema &S, FunctionDecl *FD,
     // member postfix operator (whose object argument is not a parameter).
     if (Diagnose)
       S.Diag(Loc, diag::note_defaulted_postfix_operator_no_viable)
-          << FD << (int)Kind << C;
+          << FD << !IsIncrement << C;
     return Deleted();
   }
 
diff --git a/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p2.cpp b/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p2.cpp
index b7794fc8d064ff..e38c598ac1aa0a 100644
--- a/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p2.cpp
+++ b/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p2.cpp
@@ -104,7 +104,7 @@ struct DeletedCopy {
   DeletedCopy(const DeletedCopy &) = delete; // expected-note {{'DeletedCopy' has been explicitly marked deleted here}}
   DeletedCopy operator++(int) = default; // #DeletedCopy
   // expected-warning@#DeletedCopy {{explicitly defaulted postfix increment operator is implicitly deleted}}
-  // expected-note@#DeletedCopy {{defaulted 'operator++' is implicitly deleted because it would invoke a deleted constructor}}
+  // expected-error@#DeletedCopy {{call to deleted constructor of 'DeletedCopy'}}
   // expected-note@#DeletedCopy {{replace 'default' with 'delete'}}
 };
 
@@ -113,7 +113,7 @@ struct MoveOnly {
   MoveOnly(MoveOnly &&); // expected-note {{copy constructor is implicitly deleted because 'MoveOnly' has a user-declared move constructor}}
   MoveOnly operator++(int) = default; // #MoveOnly
   // expected-warning@#MoveOnly {{explicitly defaulted postfix increment operator is implicitly deleted}}
-  // expected-note@#MoveOnly {{defaulted 'operator++' is implicitly deleted because it would invoke a deleted constructor}}
+  // expected-error@#MoveOnly {{call to implicitly-deleted copy constructor of 'MoveOnly'}}
   // expected-note@#MoveOnly {{replace 'default' with 'delete'}}
 };
 
@@ -123,7 +123,7 @@ struct AmbiguousCopy {
   AmbiguousCopy(AmbiguousCopy &, long = 0); // expected-note {{candidate constructor}}
   AmbiguousCopy operator++(int) = default; // #AmbiguousCopy
   // expected-warning@#AmbiguousCopy {{explicitly defaulted postfix increment operator is implicitly deleted}}
-  // expected-note@#AmbiguousCopy {{defaulted 'operator++' is implicitly deleted because copy construction of an lvalue of type 'AmbiguousCopy' is ambiguous}}
+  // expected-error@#AmbiguousCopy {{call to constructor of 'AmbiguousCopy' is ambiguous}}
   // expected-note@#AmbiguousCopy {{replace 'default' with 'delete'}}
 };
 
@@ -169,7 +169,7 @@ struct NotFirst {
   NotFirst operator++(int);
 };
 NotFirst NotFirst::operator++(int) = default; // expected-error {{defaulting this postfix increment operator would delete it after its first declaration}}
-// expected-note at -1 {{defaulted 'operator++' is implicitly deleted because it would invoke a deleted constructor}}
+// expected-error at -1 {{call to deleted constructor of 'NotFirst'}}
 
 // No warning is produced for a template instantiation, but the function is
 // still deleted.

>From f7160d3b0127b38f81ca8f952481fd73ae8a317e Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Sun, 18 Oct 2026 07:49:12 +0300
Subject: [PATCH 32/35] Extract the copy and destroy check for defaulted
 postfix operators

Move the checks that C can be direct-initialized from an lvalue of type C
and then destroyed, from the context of the function body, into
canCopyAndDestroyForDefaultedPostfixOperator.

Co-Authored-By: Claude Opus 5.5 <noreply at anthropic.com>
---
 clang/lib/Sema/SemaDeclCXX.cpp | 105 ++++++++++++++++++++-------------
 1 file changed, 65 insertions(+), 40 deletions(-)

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index b874660ccef743..fc9421e5a70961 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -9544,6 +9544,63 @@ static QualType getPostfixOperatorFirstParamType(const FunctionDecl *FD) {
   return FD->getParamDecl(0)->getType();
 }
 
+/// Determine whether the implicit definition of the defaulted postfix
+/// increment or decrement operator function \p FD can direct-initialize a
+/// variable of class type \p C from the lvalue \p Obj of type \p C, and then
+/// destroy it, using functions that are usable from the current context.
+///
+/// \p CheckConstexpr is called on each constructor the initialization invokes.
+/// If \p Diagnose is true, explain why the copy or destruction is not possible.
+static bool canCopyAndDestroyForDefaultedPostfixOperator(
+    Sema &S, FunctionDecl *FD, CXXRecordDecl *RD, QualType C, Expr *Obj,
+    bool Diagnose,
+    llvm::function_ref<void(const FunctionDecl *, unsigned)> CheckConstexpr) {
+  SourceLocation Loc = FD->getLocation();
+  auto InaccessibleDiag = [&](unsigned Step) {
+    return Diagnose
+               ? S.PDiag(diag::note_defaulted_postfix_operator_inaccessible)
+                     << FD << Step
+               : S.PDiag();
+  };
+
+  InitializedEntity Entity = InitializedEntity::InitializeTemporary(C);
+  InitializationKind InitKind = InitializationKind::CreateDirect(Loc, Loc, Loc);
+  InitializationSequence Seq(S, Entity, InitKind, Obj);
+  if (Seq.Failed()) {
+    // FIXME: This emits errors rather than notes explaining why the function
+    // is deleted.
+    if (Diagnose)
+      Seq.Diagnose(S, Entity, InitKind, Obj);
+    return false;
+  }
+  for (const InitializationSequence::Step &Step : Seq.steps()) {
+    if (Step.Kind != InitializationSequence::SK_ConstructorInitialization)
+      continue;
+    if (!S.isMemberAccessibleForDeletion(
+            RD, Step.Function.FoundDecl, C, Loc,
+            InaccessibleDiag(diag::DefaultedPostfixOperatorStep::Constructor)))
+      return false;
+    CheckConstexpr(Step.Function.Function,
+                   diag::DefaultedPostfixOperatorStep::Constructor);
+  }
+
+  CXXDestructorDecl *Dtor = S.LookupDestructor(RD);
+  if (!Dtor)
+    return true;
+  if (Dtor->isDeleted()) {
+    if (Diagnose) {
+      S.Diag(Loc, diag::note_defaulted_function_calls_deleted)
+          << FD << diag::DefaultedPostfixOperatorStep::Destructor
+          << /*complete object*/ 0;
+      S.NoteDeletedFunction(Dtor);
+    }
+    return false;
+  }
+  return S.isMemberAccessibleForDeletion(
+      RD, DeclAccessPair::make(Dtor, Dtor->getAccess()), C, Loc,
+      InaccessibleDiag(diag::DefaultedPostfixOperatorStep::Destructor));
+}
+
 /// Determine whether a defaulted postfix increment or decrement operator
 /// function for type \p C should be defined as deleted, as specified in C++2d
 /// [over.inc.default]p2, and whether every function it would invoke is
@@ -9601,47 +9658,15 @@ analyzeDefaultedPostfixOperator(Sema &S, FunctionDecl *FD,
   // C++2d [over.inc.default]p2:
   //   A defaulted postfix increment or decrement operator function for a type
   //   C is defined as deleted if
+  //  -- C is a class type for which overload resolution, as applied to
+  //     direct-initialization of a variable of type C from an lvalue of
+  //     type C, does not result in a usable candidate,
+  //  -- C has a destructor that is deleted or inaccessible from the context
+  //     of the function-body, or
   CXXRecordDecl *RD = C->getAsCXXRecordDecl();
-  if (RD) {
-    //  -- C is a class type for which overload resolution, as applied to
-    //     direct-initialization of a variable of type C from an lvalue of
-    //     type C, does not result in a usable candidate,
-    InitializedEntity Entity = InitializedEntity::InitializeTemporary(C);
-    InitializationKind InitKind =
-        InitializationKind::CreateDirect(Loc, Loc, Loc);
-    InitializationSequence Seq(S, Entity, InitKind, Args);
-    if (Seq.Failed()) {
-      // FIXME: This emits errors rather than notes explaining why the
-      // function is deleted.
-      if (Diagnose)
-        Seq.Diagnose(S, Entity, InitKind, Args);
-      return Deleted();
-    }
-    for (const InitializationSequence::Step &Step : Seq.steps()) {
-      if (Step.Kind != InitializationSequence::SK_ConstructorInitialization)
-        continue;
-      if (!S.isMemberAccessibleForDeletion(
-              RD, Step.Function.FoundDecl, C, Loc,
-              InaccessibleDiag(
-                  diag::DefaultedPostfixOperatorStep::Constructor)))
-        return Deleted();
-      CheckConstexpr(Step.Function.Function,
-                     diag::DefaultedPostfixOperatorStep::Constructor);
-    }
-
-    //  -- C has a destructor that is deleted or inaccessible from the context
-    //     of the function-body, or
-    if (CXXDestructorDecl *Dtor = S.LookupDestructor(RD)) {
-      if (Dtor->isDeleted()) {
-        NoteCallsDeleted(diag::DefaultedPostfixOperatorStep::Destructor, Dtor);
-        return Deleted();
-      }
-      if (!S.isMemberAccessibleForDeletion(
-              RD, DeclAccessPair::make(Dtor, Dtor->getAccess()), C, Loc,
-              InaccessibleDiag(diag::DefaultedPostfixOperatorStep::Destructor)))
-        return Deleted();
-    }
-  }
+  if (RD && !canCopyAndDestroyForDefaultedPostfixOperator(
+                S, FD, RD, C, &Obj, Diagnose, CheckConstexpr))
+    return Deleted();
 
   //  -- for an lvalue c of type C, overload resolution as applied to ++c for a
   //     postfix increment operator function or --c for a postfix decrement

>From a7088acbd59d31dfc64506499c6a75d0bbabf184 Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Sun, 18 Oct 2026 07:53:08 +0300
Subject: [PATCH 33/35] Group defaulted postfix operator check functions before
 the build functions

Co-Authored-By: Claude Opus 5.5 <noreply at anthropic.com>
---
 clang/lib/Sema/SemaDeclCXX.cpp | 178 ++++++++++++++++-----------------
 1 file changed, 89 insertions(+), 89 deletions(-)

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index fc9421e5a70961..9cf4395b41d5a3 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -9734,95 +9734,6 @@ analyzeDefaultedPostfixOperator(Sema &S, FunctionDecl *FD,
   return Info;
 }
 
-/// Build the body of a defaulted postfix increment or decrement operator
-/// function. C++2d [over.inc.default]p3:
-///   The implicit definition of a defaulted postfix increment or decrement
-///   operator function F that is not defined as deleted for a type C is
-///   equivalent to:
-///     C tmp(c);
-///     ++c;      // or --c
-///     return tmp;
-///   where tmp is a variable defined for exposition only, and c is an lvalue
-///   that denotes *this if F is an implicit object member function, or the
-///   first parameter of F otherwise.
-static StmtResult buildDefaultedPostfixOperatorBody(Sema &S, FunctionDecl *FD,
-                                                    PostfixOperatorKind Kind,
-                                                    SourceLocation Loc) {
-  Sema::CompoundScopeRAII CompoundScope(S);
-
-  // Build an expression denoting 'c'.
-  auto BuildObject = [&]() -> ExprResult {
-    if (const auto *MD = dyn_cast<CXXMethodDecl>(FD);
-        MD && MD->isImplicitObjectMemberFunction()) {
-      ExprResult This = S.ActOnCXXThis(Loc);
-      if (This.isInvalid())
-        return ExprError();
-      return S.CreateBuiltinUnaryOp(Loc, UO_Deref, This.get());
-    }
-    ParmVarDecl *Param = FD->getParamDecl(0);
-    return S.BuildDeclarationNameExpr(
-        CXXScopeSpec(), DeclarationNameInfo(Param->getDeclName(), Loc), Param);
-  };
-
-  // C tmp(c);
-  ExprResult Init = BuildObject();
-  if (Init.isInvalid())
-    return StmtError();
-  QualType C = FD->getReturnType();
-  IdentifierInfo *TmpName = &S.Context.Idents.get("__tmp");
-  VarDecl *Tmp =
-      VarDecl::Create(S.Context, S.CurContext, Loc, Loc, TmpName, C,
-                      S.Context.getTrivialTypeSourceInfo(C, Loc), SC_None);
-  Expr *InitArgs[] = {Init.get()};
-  S.AddInitializerToDecl(Tmp,
-                         ParenListExpr::Create(S.Context, Loc, InitArgs, Loc),
-                         /*DirectInit=*/true);
-  if (Tmp->isInvalidDecl())
-    return StmtError();
-  StmtResult TmpStmt = S.ActOnDeclStmt(S.ConvertDeclToDeclGroup(Tmp), Loc, Loc);
-  if (TmpStmt.isInvalid())
-    return StmtError();
-
-  // ++c; or --c;
-  ExprResult Obj = BuildObject();
-  if (Obj.isInvalid())
-    return StmtError();
-  UnaryOperatorKind Opc =
-      Kind == PostfixOperatorKind::Increment ? UO_PreInc : UO_PreDec;
-  ExprResult Prefix;
-  if (Obj.get()->getType()->isOverloadableType()) {
-    UnresolvedSet<16> Fns;
-    if (auto *DFI = FD->getDefaultedOrDeletedInfo())
-      Fns.assign(DFI->getUnqualifiedLookups().begin(),
-                 DFI->getUnqualifiedLookups().end());
-    Prefix = S.CreateOverloadedUnaryOp(Loc, Opc, Fns, Obj.get(),
-                                       /*RequiresADL=*/true);
-  } else {
-    Prefix = S.CreateBuiltinUnaryOp(Loc, Opc, Obj.get());
-  }
-  if (Prefix.isInvalid())
-    return StmtError();
-  StmtResult PrefixStmt = S.ActOnExprStmt(Prefix, /*DiscardedValue=*/true);
-  if (PrefixStmt.isInvalid())
-    return StmtError();
-
-  // return tmp;
-  ExprResult TmpRef = S.BuildDeclarationNameExpr(
-      CXXScopeSpec(), DeclarationNameInfo(TmpName, Loc), Tmp);
-  if (TmpRef.isInvalid())
-    return StmtError();
-  StmtResult Return = S.BuildReturnStmt(Loc, TmpRef.get());
-  if (Return.isInvalid())
-    return StmtError();
-  // 'tmp' is the only variable returned by this function, so it qualifies for
-  // the named return value optimization.
-  if (cast<ReturnStmt>(Return.get())->getNRVOCandidate() == Tmp)
-    Tmp->setNRVOVariable(true);
-
-  Stmt *Stmts[] = {TmpStmt.get(), PrefixStmt.get(), Return.get()};
-  return S.ActOnCompoundStmt(Loc, Loc, Stmts, /*IsStmtExpr=*/false);
-}
-
 bool Sema::CheckExplicitlyDefaultedPostfixOperator(Scope *S, FunctionDecl *FD,
                                                    PostfixOperatorKind Kind) {
   assert(Kind != PostfixOperatorKind::None &&
@@ -10045,6 +9956,95 @@ bool Sema::CheckExplicitlyDefaultedPostfixOperator(Scope *S, FunctionDecl *FD,
   return false;
 }
 
+/// Build the body of a defaulted postfix increment or decrement operator
+/// function. C++2d [over.inc.default]p3:
+///   The implicit definition of a defaulted postfix increment or decrement
+///   operator function F that is not defined as deleted for a type C is
+///   equivalent to:
+///     C tmp(c);
+///     ++c;      // or --c
+///     return tmp;
+///   where tmp is a variable defined for exposition only, and c is an lvalue
+///   that denotes *this if F is an implicit object member function, or the
+///   first parameter of F otherwise.
+static StmtResult buildDefaultedPostfixOperatorBody(Sema &S, FunctionDecl *FD,
+                                                    PostfixOperatorKind Kind,
+                                                    SourceLocation Loc) {
+  Sema::CompoundScopeRAII CompoundScope(S);
+
+  // Build an expression denoting 'c'.
+  auto BuildObject = [&]() -> ExprResult {
+    if (const auto *MD = dyn_cast<CXXMethodDecl>(FD);
+        MD && MD->isImplicitObjectMemberFunction()) {
+      ExprResult This = S.ActOnCXXThis(Loc);
+      if (This.isInvalid())
+        return ExprError();
+      return S.CreateBuiltinUnaryOp(Loc, UO_Deref, This.get());
+    }
+    ParmVarDecl *Param = FD->getParamDecl(0);
+    return S.BuildDeclarationNameExpr(
+        CXXScopeSpec(), DeclarationNameInfo(Param->getDeclName(), Loc), Param);
+  };
+
+  // C tmp(c);
+  ExprResult Init = BuildObject();
+  if (Init.isInvalid())
+    return StmtError();
+  QualType C = FD->getReturnType();
+  IdentifierInfo *TmpName = &S.Context.Idents.get("__tmp");
+  VarDecl *Tmp =
+      VarDecl::Create(S.Context, S.CurContext, Loc, Loc, TmpName, C,
+                      S.Context.getTrivialTypeSourceInfo(C, Loc), SC_None);
+  Expr *InitArgs[] = {Init.get()};
+  S.AddInitializerToDecl(Tmp,
+                         ParenListExpr::Create(S.Context, Loc, InitArgs, Loc),
+                         /*DirectInit=*/true);
+  if (Tmp->isInvalidDecl())
+    return StmtError();
+  StmtResult TmpStmt = S.ActOnDeclStmt(S.ConvertDeclToDeclGroup(Tmp), Loc, Loc);
+  if (TmpStmt.isInvalid())
+    return StmtError();
+
+  // ++c; or --c;
+  ExprResult Obj = BuildObject();
+  if (Obj.isInvalid())
+    return StmtError();
+  UnaryOperatorKind Opc =
+      Kind == PostfixOperatorKind::Increment ? UO_PreInc : UO_PreDec;
+  ExprResult Prefix;
+  if (Obj.get()->getType()->isOverloadableType()) {
+    UnresolvedSet<16> Fns;
+    if (auto *DFI = FD->getDefaultedOrDeletedInfo())
+      Fns.assign(DFI->getUnqualifiedLookups().begin(),
+                 DFI->getUnqualifiedLookups().end());
+    Prefix = S.CreateOverloadedUnaryOp(Loc, Opc, Fns, Obj.get(),
+                                       /*RequiresADL=*/true);
+  } else {
+    Prefix = S.CreateBuiltinUnaryOp(Loc, Opc, Obj.get());
+  }
+  if (Prefix.isInvalid())
+    return StmtError();
+  StmtResult PrefixStmt = S.ActOnExprStmt(Prefix, /*DiscardedValue=*/true);
+  if (PrefixStmt.isInvalid())
+    return StmtError();
+
+  // return tmp;
+  ExprResult TmpRef = S.BuildDeclarationNameExpr(
+      CXXScopeSpec(), DeclarationNameInfo(TmpName, Loc), Tmp);
+  if (TmpRef.isInvalid())
+    return StmtError();
+  StmtResult Return = S.BuildReturnStmt(Loc, TmpRef.get());
+  if (Return.isInvalid())
+    return StmtError();
+  // 'tmp' is the only variable returned by this function, so it qualifies for
+  // the named return value optimization.
+  if (cast<ReturnStmt>(Return.get())->getNRVOCandidate() == Tmp)
+    Tmp->setNRVOVariable(true);
+
+  Stmt *Stmts[] = {TmpStmt.get(), PrefixStmt.get(), Return.get()};
+  return S.ActOnCompoundStmt(Loc, Loc, Stmts, /*IsStmtExpr=*/false);
+}
+
 void Sema::DefineDefaultedPostfixOperator(SourceLocation UseLoc,
                                           FunctionDecl *FD,
                                           PostfixOperatorKind Kind) {

>From 05eadf98441a18e6122e002351c05b685470e3ac Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Sun, 18 Oct 2026 07:55:48 +0300
Subject: [PATCH 34/35] Defer all defaulted postfix operator checks in
 dependent contexts

Return early from CheckExplicitlyDefaultedPostfixOperator when the first
parameter type is dependent or the function is in a dependent context,
after the prefix operator lookup. A deduced return type in a template is
now diagnosed when the template is instantiated.

Co-Authored-By: Claude Opus 5.5 <noreply at anthropic.com>
---
 clang/lib/Sema/SemaDeclCXX.cpp                | 75 ++++++++-----------
 .../over.inc/over.inc.default/p1.cpp          | 18 +++--
 2 files changed, 44 insertions(+), 49 deletions(-)

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 9cf4395b41d5a3..8d3ee948533c90 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -9775,43 +9775,42 @@ bool Sema::CheckExplicitlyDefaultedPostfixOperator(Scope *S, FunctionDecl *FD,
   QualType FirstParamType = getPostfixOperatorFirstParamType(FD);
   QualType ReturnType = FD->getDeclaredReturnType();
 
+  // If the first parameter type is dependent, or the function is in a dependent
+  // context, defer all checks until instantiation.
+  if (FirstParamType->isDependentType() || FD->isDependentContext())
+    return false;
+
   // Check the type of the first parameter and determine the type C from it.
-  // If the parameter type is dependent, C is not known until instantiation.
-  QualType C;
-  if (!FirstParamType->isDependentType()) {
-    // For a member function, C is the enclosing class. For a non-member
-    // function, C is the class or enumeration type of its first parameter;
-    // CheckOverloadedOperatorDeclaration has already required it to have one.
-    QualType Referent = FirstParamType.getNonReferenceType();
-    C = MD ? Context.getCanonicalTagType(MD->getParent())
-           : Referent.getUnqualifiedType();
-    assert((C->isRecordType() || C->isEnumeralType()) &&
-           "postfix operator for a non-class, non-enumeration type");
-
-    // The first parameter must be a reference to C, which may be
-    // volatile-qualified but not const-qualified.
-    if (!FirstParamType->isReferenceType() ||
-        !Context.hasSameUnqualifiedType(Referent, C) ||
-        Referent.isConstQualified()) {
-      if (MD && MD->isImplicitObjectMemberFunction()) {
-        // The implicit object parameter is always a reference to the enclosing
-        // class, so the only possible problem is a const qualifier.
-        Diag(MD->getLocation(), diag::err_defaulted_postfix_operator_const)
-            << (int)Kind;
-      } else {
-        Diag(FirstParam->getBeginLoc(),
-             diag::err_defaulted_postfix_operator_param)
-            << (int)Kind << !MD << FirstParamType
-            << Context.getLValueReferenceType(C)
-            << FirstParam->getSourceRange();
-      }
-      return true;
+  // For a member function, C is the enclosing class. For a non-member function,
+  // C is the class or enumeration type of its first parameter;
+  // CheckOverloadedOperatorDeclaration has already required it to have one.
+  QualType Referent = FirstParamType.getNonReferenceType();
+  QualType C = MD ? Context.getCanonicalTagType(MD->getParent())
+                  : Referent.getUnqualifiedType();
+  assert((C->isRecordType() || C->isEnumeralType()) &&
+         "postfix operator for a non-class, non-enumeration type");
+
+  // The first parameter must be a reference to C, which may be
+  // volatile-qualified but not const-qualified.
+  if (!FirstParamType->isReferenceType() ||
+      !Context.hasSameUnqualifiedType(Referent, C) ||
+      Referent.isConstQualified()) {
+    if (MD && MD->isImplicitObjectMemberFunction()) {
+      // The implicit object parameter is always a reference to the enclosing
+      // class, so the only possible problem is a const qualifier.
+      Diag(MD->getLocation(), diag::err_defaulted_postfix_operator_const)
+          << (int)Kind;
+    } else {
+      Diag(FirstParam->getBeginLoc(),
+           diag::err_defaulted_postfix_operator_param)
+          << (int)Kind << !MD << FirstParamType
+          << Context.getLValueReferenceType(C) << FirstParam->getSourceRange();
     }
+    return true;
   }
 
-  // A deduced return type is never permitted, so diagnose it even if C is not
-  // known yet. Allowing it would need P2952R2 (auto& operator=(X&&) = default),
-  // which is not part of the standard.
+  // A deduced return type is never permitted. Allowing it would need P2952R2
+  // (auto& operator=(X&&) = default), which is not part of the standard.
   if (ReturnType->getContainedDeducedType()) {
     Diag(FD->getLocation(),
          diag::err_defaulted_postfix_operator_deduced_return_type)
@@ -9819,11 +9818,6 @@ bool Sema::CheckExplicitlyDefaultedPostfixOperator(Scope *S, FunctionDecl *FD,
     return true;
   }
 
-  // If C is not known or the return type is dependent, defer the remaining
-  // checks until instantiation.
-  if (C.isNull() || ReturnType->isDependentType())
-    return false;
-
   //    -- has a declared return type of C.
   if (!Context.hasSameType(ReturnType, C)) {
     Diag(FD->getLocation(), diag::err_defaulted_postfix_operator_return_type)
@@ -9831,11 +9825,6 @@ bool Sema::CheckExplicitlyDefaultedPostfixOperator(Scope *S, FunctionDecl *FD,
     return true;
   }
 
-  // For a defaulted function in a dependent context, defer all remaining
-  // checks until instantiation.
-  if (FD->isDependentContext())
-    return false;
-
   //    -- is defined as defaulted in C or in a context where C is complete
   if (RequireCompleteType(FD->getLocation(), C,
                           diag::err_defaulted_postfix_operator_incomplete,
diff --git a/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p1.cpp b/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p1.cpp
index 74f5164b2fa44e..96c86b6f81c6fd 100644
--- a/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p1.cpp
+++ b/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p1.cpp
@@ -308,12 +308,15 @@ template <typename T> struct DependentFriend {
 };
 DependentFriend<S> df1; // expected-note {{in instantiation of template class 'DependentFriend<S>' requested here}}
 
-// A deduced return type is never valid, so it is diagnosed even if C is not
-// known yet.
+// In a dependent context, a deduced return type is diagnosed when the template
+// is instantiated.
 template <typename T> struct DependentAuto {
-  friend auto operator++(T &, int) = default; // expected-error {{defaulted postfix increment operator cannot have a deduced return type}}
-  auto operator--(this T &, int) = default;   // expected-error {{defaulted postfix decrement operator cannot have a deduced return type}}
+  DependentAuto &operator++();
+  DependentAuto &operator--();
+  friend auto operator++(DependentAuto &, int) = default; // expected-error {{defaulted postfix increment operator cannot have a deduced return type}}
+  auto operator--(this DependentAuto &, int) = default;   // expected-error {{defaulted postfix decrement operator cannot have a deduced return type}}
 };
+DependentAuto<int> da1; // expected-note {{in instantiation of template class 'DependentAuto<int>' requested here}}
 
 template <typename T> struct DependentDeducedReturn {
   DependentDeducedReturn &operator++();
@@ -321,16 +324,19 @@ template <typename T> struct DependentDeducedReturn {
   auto operator++(int) = default;     // expected-error {{defaulted postfix increment operator cannot have a deduced return type}}
   Any auto operator--(int) = default; // expected-error {{defaulted postfix decrement operator cannot have a deduced return type}}
 };
+DependentDeducedReturn<int> ddr1; // expected-note {{in instantiation of template class 'DependentDeducedReturn<int>' requested here}}
 template <typename T> struct DependentDeducedReturn2 {
   DependentDeducedReturn2 &operator++();
   DependentDeducedReturn2 &operator--();
-  decltype(auto) operator++(int) = default;                // expected-error {{defaulted postfix increment operator cannot have a deduced return type}}
-  friend T *operator--(DependentDeducedReturn2 &, int) = default; // OK, checked when instantiated
+  decltype(auto) operator++(int) = default;                       // expected-error {{defaulted postfix increment operator cannot have a deduced return type}}
+  friend T *operator--(DependentDeducedReturn2 &, int) = default; // expected-error {{return type for defaulted postfix decrement operator must be 'DependentDeducedReturn2<int>', not 'int *'}}
 };
+DependentDeducedReturn2<int> ddr2; // expected-note {{in instantiation of template class 'DependentDeducedReturn2<int>' requested here}}
 template <typename T> struct DependentConstrainedFriend {
   DependentConstrainedFriend &operator++();
   friend Any auto operator++(DependentConstrainedFriend &, int) = default; // expected-error {{defaulted postfix increment operator cannot have a deduced return type}}
 };
+DependentConstrainedFriend<int> dcf1; // expected-note {{in instantiation of template class 'DependentConstrainedFriend<int>' requested here}}
 
 // A trailing return type that names C is not deduced.
 template <typename T> struct DependentTrailing {

>From df54c7df6668e05f3bfeaf8ff3d20dade6a3c768 Mon Sep 17 00:00:00 2001
From: bassiounix <muhammad.m.bassiouni at gmail.com>
Date: Sun, 18 Oct 2026 08:10:02 +0300
Subject: [PATCH 35/35] Test constrained and explicit object operators in
 defaulted postfix operators

Cover prefix and postfix operators that are constrained or declared with
an explicit object parameter, and add the remaining deletion and
constexpr-mismatch notes that were not yet tested: the decrement forms,
protected members, and non-constexpr constructors, conversion functions
and consteval declarations.

Co-Authored-By: Claude Fable 5.1 <noreply at anthropic.com>
---
 .../over.inc/over.inc.default/p1.cpp          |  37 +++
 .../over.inc/over.inc.default/p2.cpp          | 219 +++++++++++++++++
 .../over.inc/over.inc.default/p3.cpp          | 229 ++++++++++++++++++
 .../cxx2d-defaulted-postfix-operators.cpp     |  73 ++++++
 4 files changed, 558 insertions(+)

diff --git a/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p1.cpp b/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p1.cpp
index 96c86b6f81c6fd..bd41c2f9baa61a 100644
--- a/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p1.cpp
+++ b/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p1.cpp
@@ -374,3 +374,40 @@ template <typename T> struct DependentRestrict {
   DependentRestrict operator--(int) const __restrict__ = default; // expected-error {{defaulted member postfix decrement operator cannot be const-qualified}}
 };
 DependentRestrict<int> dres1; // expected-note {{in instantiation of template class 'DependentRestrict<int>' requested here}}
+
+// The same checks apply to the postfix decrement operator.
+struct IncompleteDecrement; // expected-note {{forward declaration of 'IncompleteDecrement'}}
+struct BefrienderDecrement {
+  friend IncompleteDecrement operator--(IncompleteDecrement &, int) = default; // expected-error {{cannot default postfix decrement operator for incomplete type 'IncompleteDecrement'}}
+};
+struct NConstDecrement { NConstDecrement &operator--(); };
+NConstDecrement operator--(const NConstDecrement &, int) = default; // expected-error {{invalid first parameter type for defaulted postfix decrement operator; found 'const NConstDecrement &', expected 'NConstDecrement &'}}
+
+// A defaulted postfix operator may be constrained. The requires-clause does
+// not make it a template.
+template <typename T> concept Small = sizeof(T) <= 4;
+template <typename T> struct Constrained {
+  Constrained &operator++();
+  Constrained &operator--();
+  Constrained operator++(int) requires Small<T> = default;                   // OK
+  Constrained operator--(this Constrained &, int) requires Small<T> = default; // OK
+  friend Constrained operator++(Constrained &&, int) requires Small<T> = default; // OK
+};
+Constrained<int> c1;
+Constrained<long long> c2;
+
+// A constrained defaulted postfix operator is checked when its class is
+// instantiated, even if its constraints are not satisfied.
+template <typename T> struct ConstrainedBad {
+  ConstrainedBad &operator++();
+  ConstrainedBad &operator--();
+  int operator++(int) requires Small<T> = default; // expected-error {{return type for defaulted postfix increment operator must be 'ConstrainedBad<long long>', not 'int'}}
+  ConstrainedBad operator--(this const ConstrainedBad &, int) requires Small<T> = default; // expected-error {{invalid explicit object parameter type for defaulted postfix decrement operator; found 'const ConstrainedBad<long long> &', expected 'ConstrainedBad<long long> &'}}
+};
+ConstrainedBad<long long> cb1; // expected-note {{in instantiation of template class 'ConstrainedBad<long long>' requested here}}
+
+// A requires-clause on a postfix operator that is not a templated entity.
+struct NotTemplated {
+  NotTemplated &operator++();
+  NotTemplated operator++(int) requires true = default; // expected-error {{non-templated function cannot have a requires clause}}
+};
diff --git a/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p2.cpp b/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p2.cpp
index e38c598ac1aa0a..e5525b84fb4b55 100644
--- a/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p2.cpp
+++ b/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p2.cpp
@@ -190,3 +190,222 @@ struct Quiet {
   Quiet operator++(int) = default;
 };
 #pragma clang diagnostic pop
+
+// The same notes are produced for the postfix decrement operator.
+struct DeletedPrefixDecrement {
+  DeletedPrefixDecrement &operator--() = delete; // expected-note {{'operator--' has been explicitly marked deleted here}}
+  DeletedPrefixDecrement operator--(int) = default; // #DeletedPrefixDecrement
+  // expected-warning@#DeletedPrefixDecrement {{explicitly defaulted postfix decrement operator is implicitly deleted}}
+  // expected-note@#DeletedPrefixDecrement {{defaulted 'operator--' is implicitly deleted because it would invoke a deleted prefix 'operator--'}}
+  // expected-note@#DeletedPrefixDecrement {{replace 'default' with 'delete'}}
+};
+struct AmbiguousBuiltinDecrement {
+  operator int &();
+  operator long &();
+  AmbiguousBuiltinDecrement operator--(int) = default; // #AmbiguousBuiltinDecrement
+  // expected-warning@#AmbiguousBuiltinDecrement {{explicitly defaulted postfix decrement operator is implicitly deleted}}
+  // expected-note@#AmbiguousBuiltinDecrement {{defaulted 'operator--' is implicitly deleted because prefix 'operator--' for an lvalue of type 'AmbiguousBuiltinDecrement' is ambiguous}}
+  // expected-note@#AmbiguousBuiltinDecrement {{built-in candidate operator--(int &)}}
+  // expected-note@#AmbiguousBuiltinDecrement {{built-in candidate operator--(long &)}}
+  // expected-note@#AmbiguousBuiltinDecrement {{replace 'default' with 'delete'}}
+};
+class PrivatePrefixDecrement {
+  PrivatePrefixDecrement &operator--(); // expected-note {{implicitly declared private here}}
+};
+PrivatePrefixDecrement operator--(PrivatePrefixDecrement &, int) = default; // #PrivatePrefixDecrementNonMember
+// expected-warning@#PrivatePrefixDecrementNonMember {{explicitly defaulted postfix decrement operator is implicitly deleted}}
+// expected-note@#PrivatePrefixDecrementNonMember {{defaulted 'operator--' is implicitly deleted because it would invoke a private prefix 'operator--' of 'PrivatePrefixDecrement'}}
+// expected-note@#PrivatePrefixDecrementNonMember {{replace 'default' with 'delete'}}
+
+// Protected members.
+class ProtectedPrefix {
+protected:
+  ProtectedPrefix &operator++(); // expected-note {{declared protected here}}
+  ProtectedPrefix &operator--(); // expected-note {{declared protected here}}
+};
+ProtectedPrefix operator++(ProtectedPrefix &, int) = default; // #ProtectedPrefixIncrement
+// expected-warning@#ProtectedPrefixIncrement {{explicitly defaulted postfix increment operator is implicitly deleted}}
+// expected-note@#ProtectedPrefixIncrement {{defaulted 'operator++' is implicitly deleted because it would invoke a protected prefix 'operator++' of 'ProtectedPrefix'}}
+// expected-note@#ProtectedPrefixIncrement {{replace 'default' with 'delete'}}
+ProtectedPrefix operator--(ProtectedPrefix &, int) = default; // #ProtectedPrefixDecrement
+// expected-warning@#ProtectedPrefixDecrement {{explicitly defaulted postfix decrement operator is implicitly deleted}}
+// expected-note@#ProtectedPrefixDecrement {{defaulted 'operator--' is implicitly deleted because it would invoke a protected prefix 'operator--' of 'ProtectedPrefix'}}
+// expected-note@#ProtectedPrefixDecrement {{replace 'default' with 'delete'}}
+// A friend of a derived class cannot use the protected member on an object of
+// the base class type.
+class ProtectedBase {
+protected:
+  ProtectedBase &operator--(); // expected-note {{can only access this member on an object of type 'DerivedFromProtectedBase'}}
+};
+struct DerivedFromProtectedBase : ProtectedBase {
+  friend ProtectedBase operator--(ProtectedBase &, int) = default; // #DerivedFromProtectedBase
+  // expected-warning@#DerivedFromProtectedBase {{explicitly defaulted postfix decrement operator is implicitly deleted}}
+  // expected-note@#DerivedFromProtectedBase {{defaulted 'operator--' is implicitly deleted because it would invoke a protected prefix 'operator--' of 'ProtectedBase'}}
+  // expected-note@#DerivedFromProtectedBase {{replace 'default' with 'delete'}}
+};
+
+class ProtectedCopy {
+protected:
+  ProtectedCopy(const ProtectedCopy &); // expected-note {{declared protected here}}
+
+public:
+  ProtectedCopy &operator++();
+  ProtectedCopy operator++(int) = default; // OK, a member has access to the copy constructor
+};
+ProtectedCopy operator++(ProtectedCopy &, int) = default; // #ProtectedCopy
+// expected-warning@#ProtectedCopy {{explicitly defaulted postfix increment operator is implicitly deleted}}
+// expected-note@#ProtectedCopy {{defaulted 'operator++' is implicitly deleted because it would invoke a protected constructor of 'ProtectedCopy'}}
+// expected-note@#ProtectedCopy {{replace 'default' with 'delete'}}
+
+class ProtectedDestructor {
+protected:
+  ~ProtectedDestructor(); // expected-note {{declared protected here}}
+
+public:
+  ProtectedDestructor &operator--();
+  ProtectedDestructor(const ProtectedDestructor &);
+  ProtectedDestructor operator--(int) = default; // OK, a member has access to the destructor
+};
+ProtectedDestructor operator--(ProtectedDestructor &, int) = default; // #ProtectedDestructor
+// expected-warning@#ProtectedDestructor {{explicitly defaulted postfix decrement operator is implicitly deleted}}
+// expected-note@#ProtectedDestructor {{defaulted 'operator--' is implicitly deleted because it would invoke a protected destructor of 'ProtectedDestructor'}}
+// expected-note@#ProtectedDestructor {{replace 'default' with 'delete'}}
+
+struct NotFirstDecrement {
+  NotFirstDecrement &operator--();
+  ~NotFirstDecrement() = delete; // expected-note {{'~NotFirstDecrement' has been explicitly marked deleted here}}
+  NotFirstDecrement operator--(int);
+};
+NotFirstDecrement NotFirstDecrement::operator--(int) = default; // expected-error {{defaulting this postfix decrement operator would delete it after its first declaration}}
+// expected-note at -1 {{defaulted 'operator--' is implicitly deleted because it would invoke a deleted destructor}}
+
+// Postfix operators with an explicit object parameter.
+struct ExplicitObjectNoPrefix {
+  ExplicitObjectNoPrefix operator++(this ExplicitObjectNoPrefix &, int) = default; // #ExplicitObjectNoPrefix
+  // expected-warning@#ExplicitObjectNoPrefix {{explicitly defaulted postfix increment operator is implicitly deleted}}
+  // expected-note@#ExplicitObjectNoPrefix 2 {{defaulted 'operator++' is implicitly deleted because there is no viable prefix 'operator++' for an lvalue of type 'ExplicitObjectNoPrefix'}}
+  // expected-note@#ExplicitObjectNoPrefix {{replace 'default' with 'delete'}}
+  // expected-note@#ExplicitObjectNoPrefix {{explicitly defaulted function was implicitly deleted here}}
+  ExplicitObjectNoPrefix operator--(this ExplicitObjectNoPrefix &&, int) = default; // #ExplicitObjectNoPrefixDecrement
+  // expected-warning@#ExplicitObjectNoPrefixDecrement {{explicitly defaulted postfix decrement operator is implicitly deleted}}
+  // expected-note@#ExplicitObjectNoPrefixDecrement 2 {{defaulted 'operator--' is implicitly deleted because there is no viable prefix 'operator--' for an lvalue of type 'ExplicitObjectNoPrefix'}}
+  // expected-note@#ExplicitObjectNoPrefixDecrement {{replace 'default' with 'delete'}}
+  // expected-note@#ExplicitObjectNoPrefixDecrement {{explicitly defaulted function was implicitly deleted here}}
+};
+void use_explicit_object_no_prefix(ExplicitObjectNoPrefix e) {
+  e++; // expected-error {{object of type 'ExplicitObjectNoPrefix' cannot be incremented because its defaulted postfix increment operator is implicitly deleted}}
+  static_cast<ExplicitObjectNoPrefix &&>(e)--; // expected-error {{object of type 'ExplicitObjectNoPrefix' cannot be decremented because its defaulted postfix decrement operator is implicitly deleted}}
+}
+// An explicit object member function is a member, so it has access to the
+// private members of its class.
+class ExplicitObjectPrivate {
+  ExplicitObjectPrivate(const ExplicitObjectPrivate &);
+  ~ExplicitObjectPrivate();
+  ExplicitObjectPrivate &operator++();
+
+public:
+  ExplicitObjectPrivate operator++(this ExplicitObjectPrivate &, int) = default; // OK
+};
+struct ExplicitObjectDeletedPrefix {
+  ExplicitObjectDeletedPrefix &operator++() = delete; // expected-note {{'operator++' has been explicitly marked deleted here}}
+  ExplicitObjectDeletedPrefix operator++(this ExplicitObjectDeletedPrefix &, int) = default; // #ExplicitObjectDeletedPrefix
+  // expected-warning@#ExplicitObjectDeletedPrefix {{explicitly defaulted postfix increment operator is implicitly deleted}}
+  // expected-note@#ExplicitObjectDeletedPrefix {{defaulted 'operator++' is implicitly deleted because it would invoke a deleted prefix 'operator++'}}
+  // expected-note@#ExplicitObjectDeletedPrefix {{replace 'default' with 'delete'}}
+};
+
+// Prefix operators with an explicit object parameter. The prefix operator is
+// applied to an lvalue of type C, so an explicit object parameter of type
+// "rvalue reference to C" is not viable.
+struct ExplicitObjectPrefix {
+  ExplicitObjectPrefix &operator++(this ExplicitObjectPrefix &);
+  ExplicitObjectPrefix operator++(int) = default; // OK
+};
+struct RvalueExplicitObjectPrefix {
+  RvalueExplicitObjectPrefix &operator++(this RvalueExplicitObjectPrefix &&);
+  RvalueExplicitObjectPrefix operator++(int) = default; // #RvalueExplicitObjectPrefix
+  // expected-warning@#RvalueExplicitObjectPrefix {{explicitly defaulted postfix increment operator is implicitly deleted}}
+  // expected-note@#RvalueExplicitObjectPrefix {{defaulted 'operator++' is implicitly deleted because there is no viable prefix 'operator++' for an lvalue of type 'RvalueExplicitObjectPrefix'}}
+  // expected-note@#RvalueExplicitObjectPrefix {{replace 'default' with 'delete'}}
+};
+struct DeletedExplicitObjectPrefix {
+  DeletedExplicitObjectPrefix &operator--(this DeletedExplicitObjectPrefix &) = delete; // expected-note {{'operator--' has been explicitly marked deleted here}}
+  DeletedExplicitObjectPrefix operator--(int) = default; // #DeletedExplicitObjectPrefix
+  // expected-warning@#DeletedExplicitObjectPrefix {{explicitly defaulted postfix decrement operator is implicitly deleted}}
+  // expected-note@#DeletedExplicitObjectPrefix {{defaulted 'operator--' is implicitly deleted because it would invoke a deleted prefix 'operator--'}}
+  // expected-note@#DeletedExplicitObjectPrefix {{replace 'default' with 'delete'}}
+};
+class PrivateExplicitObjectPrefix {
+  PrivateExplicitObjectPrefix &operator++(this PrivateExplicitObjectPrefix &); // expected-note {{implicitly declared private here}}
+};
+PrivateExplicitObjectPrefix operator++(PrivateExplicitObjectPrefix &, int) = default; // #PrivateExplicitObjectPrefix
+// expected-warning@#PrivateExplicitObjectPrefix {{explicitly defaulted postfix increment operator is implicitly deleted}}
+// expected-note@#PrivateExplicitObjectPrefix {{defaulted 'operator++' is implicitly deleted because it would invoke a private prefix 'operator++' of 'PrivateExplicitObjectPrefix'}}
+// expected-note@#PrivateExplicitObjectPrefix {{replace 'default' with 'delete'}}
+// A deduced explicit object parameter makes the prefix operator a template.
+// The deleted specialization is selected by overload resolution.
+struct DeletedDeducedPrefix {
+  template <typename Self> DeletedDeducedPrefix &operator++(this Self &&) = delete; // expected-note {{'operator++<DeletedDeducedPrefix &>' has been explicitly marked deleted here}}
+  DeletedDeducedPrefix operator++(int) = default; // #DeletedDeducedPrefix
+  // expected-warning@#DeletedDeducedPrefix {{explicitly defaulted postfix increment operator is implicitly deleted}}
+  // expected-note@#DeletedDeducedPrefix {{defaulted 'operator++' is implicitly deleted because it would invoke a deleted prefix 'operator++'}}
+  // expected-note@#DeletedDeducedPrefix {{replace 'default' with 'delete'}}
+};
+
+// Constrained prefix operators. An unsatisfied constraint makes the candidate
+// non-viable.
+template <typename T> concept Small = sizeof(T) <= 4;
+template <typename T> struct ConstrainedPrefix {
+  T v;
+  ConstrainedPrefix &operator++() requires Small<T>;
+  ConstrainedPrefix &operator--(this ConstrainedPrefix &) requires Small<T>;
+  ConstrainedPrefix operator++(int) = default; // #ConstrainedPrefixIncrement
+  // expected-note@#ConstrainedPrefixIncrement {{explicitly defaulted function was implicitly deleted here}}
+  // expected-note@#ConstrainedPrefixIncrement {{defaulted 'operator++' is implicitly deleted because there is no viable prefix 'operator++' for an lvalue of type 'ConstrainedPrefix<long long>'}}
+  friend ConstrainedPrefix operator--(ConstrainedPrefix &, int) = default; // #ConstrainedPrefixDecrement
+  // expected-note@#ConstrainedPrefixDecrement {{explicitly defaulted function was implicitly deleted here}}
+  // expected-note@#ConstrainedPrefixDecrement {{defaulted 'operator--' is implicitly deleted because there is no viable prefix 'operator--' for an lvalue of type 'ConstrainedPrefix<long long>'}}
+};
+void use_constrained_prefix(ConstrainedPrefix<int> a, ConstrainedPrefix<long long> b) {
+  a++; // OK
+  a--; // OK
+  b++; // expected-error {{object of type 'ConstrainedPrefix<long long>' cannot be incremented because its defaulted postfix increment operator is implicitly deleted}}
+  b--; // expected-error {{object of type 'ConstrainedPrefix<long long>' cannot be decremented because its defaulted postfix decrement operator is implicitly deleted}}
+}
+struct ConstrainedNonMemberPrefix { long long a, b; };
+template <typename T> requires (__is_same(T, ConstrainedNonMemberPrefix) && Small<T>) T &operator++(T &);
+ConstrainedNonMemberPrefix operator++(ConstrainedNonMemberPrefix &, int) = default; // #ConstrainedNonMemberPrefix
+// expected-warning@#ConstrainedNonMemberPrefix {{explicitly defaulted postfix increment operator is implicitly deleted}}
+// expected-note@#ConstrainedNonMemberPrefix {{defaulted 'operator++' is implicitly deleted because there is no viable prefix 'operator++' for an lvalue of type 'ConstrainedNonMemberPrefix'}}
+// expected-note@#ConstrainedNonMemberPrefix {{replace 'default' with 'delete'}}
+// A deleted candidate is still viable if its constraints are satisfied.
+struct DeletedConstrainedPrefix {};
+template <typename T> requires __is_same(T, DeletedConstrainedPrefix) T &operator--(T &) = delete; // expected-note {{'operator--<DeletedConstrainedPrefix>' has been explicitly marked deleted here}}
+DeletedConstrainedPrefix operator--(DeletedConstrainedPrefix &, int) = default; // #DeletedConstrainedPrefix
+// expected-warning@#DeletedConstrainedPrefix {{explicitly defaulted postfix decrement operator is implicitly deleted}}
+// expected-note@#DeletedConstrainedPrefix {{defaulted 'operator--' is implicitly deleted because it would invoke a deleted prefix 'operator--'}}
+// expected-note@#DeletedConstrainedPrefix {{replace 'default' with 'delete'}}
+// The more constrained prefix operator is selected.
+template <typename T> struct MoreConstrainedPrefix {
+  MoreConstrainedPrefix &operator++() requires Small<T>;
+  MoreConstrainedPrefix &operator++() requires Small<T> && __is_same(T, int) = delete; // expected-note {{'operator++' has been explicitly marked deleted here}}
+  MoreConstrainedPrefix operator++(int) = default; // #MoreConstrainedPrefix
+  // expected-note@#MoreConstrainedPrefix {{explicitly defaulted function was implicitly deleted here}}
+  // expected-note@#MoreConstrainedPrefix {{defaulted 'operator++' is implicitly deleted because it would invoke a deleted prefix 'operator++'}}
+};
+void use_more_constrained_prefix(MoreConstrainedPrefix<short> a, MoreConstrainedPrefix<int> b) {
+  a++; // OK
+  b++; // expected-error {{object of type 'MoreConstrainedPrefix<int>' cannot be incremented because its defaulted postfix increment operator is implicitly deleted}}
+}
+
+// Constrained postfix operators. The constraints of the postfix operator do
+// not affect whether it is defined as deleted, only whether it is viable.
+template <typename T> struct ConstrainedPostfix {
+  ConstrainedPostfix operator++(int) requires Small<T> = default; // #ConstrainedPostfix
+  // expected-note@#ConstrainedPostfix {{explicitly defaulted function was implicitly deleted here}}
+  // expected-note@#ConstrainedPostfix {{defaulted 'operator++' is implicitly deleted because there is no viable prefix 'operator++' for an lvalue of type 'ConstrainedPostfix<int>'}}
+};
+void use_constrained_postfix(ConstrainedPostfix<int> a, ConstrainedPostfix<long long> b) {
+  a++; // expected-error {{object of type 'ConstrainedPostfix<int>' cannot be incremented because its defaulted postfix increment operator is implicitly deleted}}
+  b++; // expected-error {{cannot increment value of type 'ConstrainedPostfix<long long>'}}
+}
diff --git a/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p3.cpp b/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p3.cpp
index 64d3d6ad8638b6..764fbbed141b2d 100644
--- a/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p3.cpp
+++ b/clang/test/CXX/over/over.oper/over.inc/over.inc.default/p3.cpp
@@ -341,4 +341,233 @@ struct NoexceptNonMember {
 NoexceptNonMember operator++(NoexceptNonMember &, int) = default;
 extern NoexceptNonMember noexcept_non_member;
 static_assert(noexcept(noexcept_non_member++));
+
+// Prefix operators with an explicit object parameter.
+struct ExplicitObjectPrefix {
+  int v;
+  constexpr ExplicitObjectPrefix &operator++(this ExplicitObjectPrefix &self) { ++self.v; return self; }
+  constexpr ExplicitObjectPrefix &operator--(this ExplicitObjectPrefix &self) { --self.v; return self; }
+  ExplicitObjectPrefix operator++(int) = default;
+  friend ExplicitObjectPrefix operator--(ExplicitObjectPrefix &, int) = default;
+};
+constexpr int explicit_object_prefix() {
+  ExplicitObjectPrefix e{1};
+  ExplicitObjectPrefix a = e++;
+  ExplicitObjectPrefix b = e--;
+  return a.v * 100 + b.v * 10 + e.v;
+}
+static_assert(explicit_object_prefix() == 121);
+
+// A deduced explicit object parameter deduces an lvalue of type C.
+struct DeducedObjectPrefix {
+  int v;
+  constexpr auto &operator++(this auto &&self) {
+    static_assert(__is_same(decltype(self), DeducedObjectPrefix &));
+    ++self.v;
+    return self;
+  }
+  DeducedObjectPrefix operator++(int) = default;
+};
+constexpr int deduced_object_prefix() {
+  DeducedObjectPrefix d{1};
+  DeducedObjectPrefix a = d++;
+  return a.v * 10 + d.v;
+}
+static_assert(deduced_object_prefix() == 12);
+
+// The explicit object parameter need not be a non-const lvalue reference; the
+// prefix operator is called like any other.
+struct ByValueObjectPrefix {
+  int v;
+  constexpr ByValueObjectPrefix operator++(this ByValueObjectPrefix self) { return {self.v + 1}; }
+  ByValueObjectPrefix operator++(int) = default;
+};
+constexpr int by_value_object_prefix() {
+  ByValueObjectPrefix b{1};
+  ByValueObjectPrefix a = b++;
+  return a.v * 10 + b.v;
+}
+static_assert(by_value_object_prefix() == 11);
+struct ConstObjectPrefix {
+  mutable int v;
+  constexpr const ConstObjectPrefix &operator--(this const ConstObjectPrefix &self) { --self.v; return self; }
+  ConstObjectPrefix operator--(int) = default;
+};
+constexpr int const_object_prefix() {
+  ConstObjectPrefix c{1};
+  ConstObjectPrefix a = c--;
+  return a.v * 10 + c.v;
+}
+static_assert(const_object_prefix() == 10);
+
+// Postfix operators with an explicit object parameter.
+struct ExplicitObjectRvalue {
+  int v;
+  constexpr ExplicitObjectRvalue &operator++() { ++v; return *this; }
+  ExplicitObjectRvalue operator++(this ExplicitObjectRvalue &&, int) = default;
+};
+constexpr int explicit_object_rvalue() {
+  ExplicitObjectRvalue e{1};
+  ExplicitObjectRvalue a = static_cast<ExplicitObjectRvalue &&>(e)++;
+  return a.v * 10 + e.v;
+}
+static_assert(explicit_object_rvalue() == 12);
+struct ExplicitObjectVolatile {
+  ExplicitObjectVolatile();
+  ExplicitObjectVolatile(const volatile ExplicitObjectVolatile &);
+  ExplicitObjectVolatile(ExplicitObjectVolatile &&);
+  ExplicitObjectVolatile &operator++(this volatile ExplicitObjectVolatile &);
+  ExplicitObjectVolatile operator++(this volatile ExplicitObjectVolatile &, int) = default;
+};
+void explicit_object_volatile() {
+  volatile ExplicitObjectVolatile v;
+  v++;
+}
+// Both operators have an explicit object parameter.
+template <typename T> struct ExplicitObjectBoth {
+  T v;
+  constexpr ExplicitObjectBoth &operator++(this ExplicitObjectBoth &self) { ++self.v; return self; }
+  constexpr ExplicitObjectBoth &operator--(this auto &self) { --self.v; return self; }
+  ExplicitObjectBoth operator++(this ExplicitObjectBoth &self, int) = default;
+  ExplicitObjectBoth operator--(this ExplicitObjectBoth &self, int) = default;
+};
+constexpr int explicit_object_both() {
+  ExplicitObjectBoth<int> e{1};
+  ExplicitObjectBoth<int> a = e++;
+  ExplicitObjectBoth<int> b = e--;
+  return a.v * 100 + b.v * 10 + e.v;
+}
+static_assert(explicit_object_both() == 121);
+// An explicit object postfix operator with an implicit object prefix operator
+// is called the same way.
+struct ExplicitObjectPostfixOnly {
+  int v;
+  constexpr ExplicitObjectPostfixOnly &operator--() { --v; return *this; }
+  ExplicitObjectPostfixOnly operator--(this ExplicitObjectPostfixOnly &self, int) = default;
+};
+constexpr int explicit_object_postfix_only() {
+  ExplicitObjectPostfixOnly e{1};
+  ExplicitObjectPostfixOnly a = e--;
+  return a.v * 10 + e.v;
+}
+static_assert(explicit_object_postfix_only() == 10);
+
+// Constrained prefix operators.
+template <typename T> concept Small = sizeof(T) <= 4;
+template <typename T> concept Int = __is_same(T, int);
+template <typename T> struct ConstrainedPrefix {
+  T v;
+  constexpr ConstrainedPrefix &operator++() requires Small<T> { ++v; return *this; }
+  constexpr ConstrainedPrefix &operator--(this ConstrainedPrefix &self) requires Small<T> { --self.v; return self; }
+  ConstrainedPrefix operator++(int) = default;
+  ConstrainedPrefix operator--(int) = default;
+};
+constexpr int constrained_prefix() {
+  ConstrainedPrefix<int> c{1};
+  ConstrainedPrefix<int> a = c++;
+  ConstrainedPrefix<int> b = c--;
+  return a.v * 100 + b.v * 10 + c.v;
+}
+static_assert(constrained_prefix() == 121);
+// The more constrained prefix operator is selected.
+template <typename T> struct MoreConstrainedPrefix {
+  T v;
+  constexpr MoreConstrainedPrefix &operator++() requires Small<T> { v += 1; return *this; }
+  constexpr MoreConstrainedPrefix &operator++() requires Small<T> && Int<T> { v += 10; return *this; }
+  MoreConstrainedPrefix operator++(int) = default;
+};
+constexpr int more_constrained_prefix() {
+  MoreConstrainedPrefix<int> i{1};
+  MoreConstrainedPrefix<int> a = i++;
+  MoreConstrainedPrefix<short> s{1};
+  MoreConstrainedPrefix<short> b = s++;
+  return a.v * 1000 + i.v * 100 + b.v * 10 + s.v;
+}
+static_assert(more_constrained_prefix() == 1 * 1000 + 11 * 100 + 1 * 10 + 2);
+// A constrained non-member prefix operator template.
+struct ConstrainedNonMemberPrefix { int v; };
+template <typename T> requires __is_same(T, ConstrainedNonMemberPrefix)
+constexpr T &operator++(T &t) { ++t.v; return t; }
+constexpr ConstrainedNonMemberPrefix operator++(ConstrainedNonMemberPrefix &, int) = default;
+constexpr int constrained_non_member_prefix() {
+  ConstrainedNonMemberPrefix c{1};
+  ConstrainedNonMemberPrefix a = c++;
+  return a.v * 10 + c.v;
+}
+static_assert(constrained_non_member_prefix() == 12);
+// The exception specification depends on the selected prefix operator.
+template <typename T> struct ConstrainedPrefixNoexcept {
+  T v;
+  ConstrainedPrefixNoexcept &operator++() noexcept requires Small<T>;
+  ConstrainedPrefixNoexcept &operator++();
+  ConstrainedPrefixNoexcept operator++(int) = default;
+};
+static_assert(noexcept(ConstrainedPrefixNoexcept<int>{}++));
+static_assert(!noexcept(ConstrainedPrefixNoexcept<long long>{}++));
+
+// Constrained postfix operators.
+template <typename T> struct ConstrainedPostfix {
+  T v;
+  constexpr ConstrainedPostfix &operator++() { ++v; return *this; }
+  constexpr ConstrainedPostfix &operator--() { --v; return *this; }
+  ConstrainedPostfix operator++(int) requires Small<T> = default;
+  friend ConstrainedPostfix operator--(ConstrainedPostfix &, int) requires Small<T> = default;
+};
+constexpr int constrained_postfix() {
+  ConstrainedPostfix<int> c{1};
+  ConstrainedPostfix<int> a = c++;
+  ConstrainedPostfix<int> b = c--;
+  return a.v * 100 + b.v * 10 + c.v;
+}
+static_assert(constrained_postfix() == 121);
+void constrained_postfix_unsatisfied(ConstrainedPostfix<long long> c) {
+  c++; // expected-error {{cannot increment value of type 'ConstrainedPostfix<long long>'}}
+  c--; // expected-error {{cannot decrement value of type 'ConstrainedPostfix<long long>'}}
+}
+// A defaulted postfix operator can be more constrained than a user-provided
+// one, and vice versa.
+template <typename T> struct MoreConstrainedPostfix {
+  T v;
+  constexpr MoreConstrainedPostfix &operator++() { v += 1; return *this; }
+  constexpr MoreConstrainedPostfix operator++(int) requires Small<T> { MoreConstrainedPostfix t = *this; v += 10; return t; }
+  MoreConstrainedPostfix operator++(int) requires Small<T> && Int<T> = default;
+  constexpr MoreConstrainedPostfix &operator--() { v -= 1; return *this; }
+  MoreConstrainedPostfix operator--(int) requires Small<T> = default;
+  constexpr MoreConstrainedPostfix operator--(int) requires Small<T> && Int<T> { MoreConstrainedPostfix t = *this; v -= 10; return t; }
+};
+constexpr int more_constrained_postfix() {
+  MoreConstrainedPostfix<int> i{1};
+  MoreConstrainedPostfix<int> a = i++;
+  MoreConstrainedPostfix<int> b = i--;
+  MoreConstrainedPostfix<short> s{1};
+  MoreConstrainedPostfix<short> c = s++;
+  MoreConstrainedPostfix<short> d = s--;
+  return a.v * 100000 + b.v * 10000 + i.v * 1000 + c.v * 100 + d.v * 10 + s.v;
+}
+static_assert(more_constrained_postfix() == 1 * 100000 + 2 * 10000 + (-8) * 1000 + 1 * 100 + 11 * 10 + 10);
+// A constrained explicit object postfix operator.
+template <typename T> struct ConstrainedExplicitObjectPostfix {
+  T v;
+  constexpr ConstrainedExplicitObjectPostfix &operator++(this ConstrainedExplicitObjectPostfix &self) requires Small<T> { ++self.v; return self; }
+  ConstrainedExplicitObjectPostfix operator++(this ConstrainedExplicitObjectPostfix &self, int) requires Small<T> = default;
+};
+constexpr int constrained_explicit_object_postfix() {
+  ConstrainedExplicitObjectPostfix<int> c{1};
+  ConstrainedExplicitObjectPostfix<int> a = c++;
+  return a.v * 10 + c.v;
+}
+static_assert(constrained_explicit_object_postfix() == 12);
+void constrained_explicit_object_postfix_unsatisfied(ConstrainedExplicitObjectPostfix<long long> c) {
+  c++; // expected-error {{cannot increment value of type 'ConstrainedExplicitObjectPostfix<long long>'}}
+}
+// The exception specification of an explicit object postfix operator.
+struct ExplicitObjectNoexcept {
+  ExplicitObjectNoexcept &operator++(this ExplicitObjectNoexcept &) noexcept;
+  ExplicitObjectNoexcept operator++(this ExplicitObjectNoexcept &, int) = default;
+  ExplicitObjectNoexcept &operator--(this ExplicitObjectNoexcept &);
+  ExplicitObjectNoexcept operator--(this ExplicitObjectNoexcept &, int) = default;
+};
+extern ExplicitObjectNoexcept explicit_object_noexcept;
+static_assert(noexcept(explicit_object_noexcept++));
+static_assert(!noexcept(explicit_object_noexcept--));
 #endif
diff --git a/clang/test/SemaCXX/cxx2d-defaulted-postfix-operators.cpp b/clang/test/SemaCXX/cxx2d-defaulted-postfix-operators.cpp
index da75d03dac96d9..6b624852394f4b 100644
--- a/clang/test/SemaCXX/cxx2d-defaulted-postfix-operators.cpp
+++ b/clang/test/SemaCXX/cxx2d-defaulted-postfix-operators.cpp
@@ -105,4 +105,77 @@ constexpr int use_constexpr() {
   return a.v * 10 + c.v;
 }
 static_assert(use_constexpr() == 12, "");
+
+// Each function invoked by the implicit definition is checked.
+struct NotConstexprCopy {
+  int v;
+  constexpr NotConstexprCopy(int v) : v(v) {}
+  NotConstexprCopy(const NotConstexprCopy &); // pre23-note {{non-constexpr constructor declared here}}
+  constexpr NotConstexprCopy &operator++();
+  constexpr NotConstexprCopy operator++(int) = default;
+  // pre23-error at -1 {{defaulted definition of postfix increment operator cannot be declared constexpr because it invokes a non-constexpr function}}
+  // pre2d-warning at -2 {{defaulted postfix increment and decrement operators are a C++2d extension}}
+  // compat-warning at -3 {{defaulted postfix increment and decrement operators are incompatible with C++ standards before C++2d}}
+};
+struct NotConstexprDecrement {
+  NotConstexprDecrement &operator--(); // pre23-note {{non-constexpr prefix 'operator--' declared here}}
+  constexpr NotConstexprDecrement operator--(int) = default;
+  // pre23-error at -1 {{defaulted definition of postfix decrement operator cannot be declared constexpr because it invokes a non-constexpr function}}
+  // pre2d-warning at -2 {{defaulted postfix increment and decrement operators are a C++2d extension}}
+  // compat-warning at -3 {{defaulted postfix increment and decrement operators are incompatible with C++ standards before C++2d}}
+};
+struct NotConstexprConversion {
+  int v;
+  operator int &(); // pre23-note {{non-constexpr conversion function declared here}}
+  constexpr NotConstexprConversion operator++(int) = default;
+  // pre23-error at -1 {{defaulted definition of postfix increment operator cannot be declared constexpr because it invokes a non-constexpr function}}
+  // pre2d-warning at -2 {{defaulted postfix increment and decrement operators are a C++2d extension}}
+  // compat-warning at -3 {{defaulted postfix increment and decrement operators are incompatible with C++ standards before C++2d}}
+};
+struct NotConstexprSeveral {
+  int v;
+  constexpr NotConstexprSeveral(int v) : v(v) {}
+  NotConstexprSeveral(const NotConstexprSeveral &); // pre23-note {{non-constexpr constructor declared here}}
+  NotConstexprSeveral &operator++(); // pre23-note {{non-constexpr prefix 'operator++' declared here}}
+  constexpr NotConstexprSeveral operator++(int) = default;
+  // pre23-error at -1 {{defaulted definition of postfix increment operator cannot be declared constexpr because it invokes a non-constexpr function}}
+  // pre2d-warning at -2 {{defaulted postfix increment and decrement operators are a C++2d extension}}
+  // compat-warning at -3 {{defaulted postfix increment and decrement operators are incompatible with C++ standards before C++2d}}
+};
+struct NotConstexprNonMember {
+  NotConstexprNonMember &operator++(); // pre23-note {{non-constexpr prefix 'operator++' declared here}}
+};
+constexpr NotConstexprNonMember operator++(NotConstexprNonMember &, int) = default;
+// pre23-error at -1 {{defaulted definition of postfix increment operator cannot be declared constexpr because it invokes a non-constexpr function}}
+// pre2d-warning at -2 {{defaulted postfix increment and decrement operators are a C++2d extension}}
+// compat-warning at -3 {{defaulted postfix increment and decrement operators are incompatible with C++ standards before C++2d}}
+enum NotConstexprEnum { nce };
+NotConstexprEnum &operator++(NotConstexprEnum &); // pre23-note {{non-constexpr prefix 'operator++' declared here}}
+constexpr NotConstexprEnum operator++(NotConstexprEnum &, int) = default;
+// pre23-error at -1 {{defaulted definition of postfix increment operator cannot be declared constexpr because it invokes a non-constexpr function}}
+// pre2d-warning at -2 {{defaulted postfix increment and decrement operators are a C++2d extension}}
+// compat-warning at -3 {{defaulted postfix increment and decrement operators are incompatible with C++ standards before C++2d}}
+#endif
+
+#if __cplusplus >= 202002L
+struct NotConstevalCompatible {
+  NotConstevalCompatible &operator++(); // pre23-note {{non-constexpr prefix 'operator++' declared here}}
+  consteval NotConstevalCompatible operator++(int) = default;
+  // pre23-error at -1 {{defaulted definition of postfix increment operator cannot be declared consteval because it invokes a non-constexpr function}}
+  // pre2d-warning at -2 {{defaulted postfix increment and decrement operators are a C++2d extension}}
+  // compat-warning at -3 {{defaulted postfix increment and decrement operators are incompatible with C++ standards before C++2d}}
+};
+struct Consteval {
+  int v;
+  constexpr Consteval &operator++() { ++v; return *this; }
+  consteval Consteval operator++(int) = default;
+  // pre2d-warning at -1 {{defaulted postfix increment and decrement operators are a C++2d extension}}
+  // compat-warning at -2 {{defaulted postfix increment and decrement operators are incompatible with C++ standards before C++2d}}
+};
+consteval int use_consteval() {
+  Consteval c{1};
+  Consteval a = c++;
+  return a.v * 10 + c.v;
+}
+static_assert(use_consteval() == 12);
 #endif



More information about the cfe-commits mailing list