Skip to content

[clang] Implement CTAD for type alias template. #77890

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Mar 8, 2024

Conversation

hokein
Copy link
Collaborator

@hokein hokein commented Jan 12, 2024

Fixes #54051

This patch implements the C++20 feature -- CTAD for alias templates (P1814R0, specified in https://eel.is/c++draft/over.match.class.deduct#3). It is an initial patch:

  • it cover major pieces, thus it works for most cases;
  • the big missing piece is to implement the associated constraints (over.match.class.deduct#3.3) for the synthesized deduction guides, see the FIXME in code and tests;
  • Some enhancements on the TreeTransform&TemplateInstantiator to allow performing instantiation on BuildingDeductionGuides mode;

Copy link

github-actions bot commented Jan 12, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

@cor3ntin
Copy link
Contributor

I'm thrilled to see work in this area. Thanks!

Note that Clang 18 is going to branch soon, so this PR is going to target Clang 19.
However, because it took a while to stabilize other CTAD related works, i think it might be worth it to try to complete that work early in the Clang 19 cycle, so that we have a few months to weed out the bugs.

I do believe this is the last unimplemented features for our C++20 support (outside of bugs and modules)

@cor3ntin cor3ntin added c++20 clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Jan 12, 2024
@hokein hokein force-pushed the ctad-alias-template-draft branch 3 times, most recently from a4b028d to 0f2d4fe Compare January 22, 2024 10:51
@hokein
Copy link
Collaborator Author

hokein commented Jan 22, 2024

Note that Clang 18 is going to branch soon, so this PR is going to target Clang 19.

Yes, this patch will not be ready before the 18 release cut, so targeting clang 19 sounds like a good plan.

As you may notice, the implementation is incomplete, and is likely far from perfect, I can see two alternatives to proceed:

  1. we keep improving it here, and landing it when it is in good/perfect shape;
  2. find a way to land a relatively-good initial version, and continue to develop it incrementally;

Re 1), it is hard to predict the timeline, I'd like to finish it soon, but I also have other tasks with higher priority.
2) seems like a good option, we can hide this experimental feature with a new -cc1 flag, when this feature is ready, we enable it by default. It is probably not a common practice for a narrow feature like this.

Any thoughts? @cor3ntin, @shafik, @AaronBallman

@hokein hokein force-pushed the ctad-alias-template-draft branch from 0f2d4fe to 8f7d83a Compare January 29, 2024 08:38
@hokein hokein changed the title [clang] WIP: Implement CTAD for type alias template. [clang] Implement CTAD for type alias template. Jan 29, 2024
@hokein hokein force-pushed the ctad-alias-template-draft branch from 8f7d83a to becb1bd Compare January 29, 2024 08:58
@hokein hokein marked this pull request as ready for review January 29, 2024 09:03
@llvmbot llvmbot added the clang Clang issues not falling into any other category label Jan 29, 2024
@llvmbot
Copy link
Member

llvmbot commented Jan 29, 2024

@llvm/pr-subscribers-clang

Author: Haojian Wu (hokein)

Changes

Fixes #54051

This patch implements the C++20 feature -- CTAD for alias templates.
It is an initial patch, which covers most of pieces, the major missing piece is to implement the associated constraints (over.match.class.deduct#3.3) for the synthesized deduction guides (we can address it in a followup).

This patch also refactors the existing ConvertConstructorToDeductionGuideTransform to allow code reuse.

CC @ilya-biryukov, @sam-mccall , @usx95


Patch is 52.50 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/77890.diff

10 Files Affected:

  • (modified) clang/include/clang/Sema/Sema.h (+11-3)
  • (modified) clang/lib/Sema/CMakeLists.txt (+1)
  • (added) clang/lib/Sema/CTAD.cpp (+209)
  • (added) clang/lib/Sema/CTAD.h (+65)
  • (modified) clang/lib/Sema/SemaInit.cpp (+367-7)
  • (modified) clang/lib/Sema/SemaTemplate.cpp (+23-145)
  • (modified) clang/lib/Sema/SemaTemplateDeduction.cpp (+9)
  • (modified) clang/lib/Sema/SemaTemplateInstantiate.cpp (+22-3)
  • (modified) clang/lib/Sema/SemaTemplateInstantiateDecl.cpp (+16-9)
  • (added) clang/test/SemaCXX/cxx20-ctad-type-alias.cpp (+133)
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index b5946e3f3178ff..c3fba8add4b660 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -9305,6 +9305,12 @@ class Sema final {
                           const TemplateArgumentList &TemplateArgs,
                           sema::TemplateDeductionInfo &Info);
 
+  TemplateDeductionResult DeduceTemplateArguments(
+      TemplateParameterList *TemplateParams, ArrayRef<TemplateArgument> Ps,
+      ArrayRef<TemplateArgument> As, sema::TemplateDeductionInfo &Info,
+      SmallVectorImpl<DeducedTemplateArgument> &Deduced,
+      bool NumberOfArgumentsMustMatch);
+
   TemplateDeductionResult SubstituteExplicitTemplateArguments(
       FunctionTemplateDecl *FunctionTemplate,
       TemplateArgumentListInfo &ExplicitTemplateArgs,
@@ -10457,9 +10463,11 @@ class Sema final {
       SourceLocation PointOfInstantiation, FunctionDecl *Decl,
       ArrayRef<TemplateArgument> TemplateArgs,
       ConstraintSatisfaction &Satisfaction);
-  FunctionDecl *InstantiateFunctionDeclaration(FunctionTemplateDecl *FTD,
-                                               const TemplateArgumentList *Args,
-                                               SourceLocation Loc);
+  FunctionDecl *InstantiateFunctionDeclaration(
+      FunctionTemplateDecl *FTD, const TemplateArgumentList *Args,
+      SourceLocation Loc,
+      CodeSynthesisContext::SynthesisKind CSC =
+          CodeSynthesisContext::ExplicitTemplateArgumentSubstitution);
   void InstantiateFunctionDefinition(SourceLocation PointOfInstantiation,
                                      FunctionDecl *Function,
                                      bool Recursive = false,
diff --git a/clang/lib/Sema/CMakeLists.txt b/clang/lib/Sema/CMakeLists.txt
index 1856a88e9a3271..7a55406171ac74 100644
--- a/clang/lib/Sema/CMakeLists.txt
+++ b/clang/lib/Sema/CMakeLists.txt
@@ -14,6 +14,7 @@ clang_tablegen(OpenCLBuiltins.inc -gen-clang-opencl-builtins
 
 add_clang_library(clangSema
   AnalysisBasedWarnings.cpp
+  CTAD.cpp
   CodeCompleteConsumer.cpp
   DeclSpec.cpp
   DelayedDiagnostic.cpp
diff --git a/clang/lib/Sema/CTAD.cpp b/clang/lib/Sema/CTAD.cpp
new file mode 100644
index 00000000000000..745878c717fc4c
--- /dev/null
+++ b/clang/lib/Sema/CTAD.cpp
@@ -0,0 +1,209 @@
+//===--- CTAD.cpp - -------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "CTAD.h"
+#include "TreeTransform.h"
+#include "TypeLocBuilder.h"
+#include "clang/AST/ASTConsumer.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/ASTMutationListener.h"
+#include "clang/AST/ASTStructuralEquivalence.h"
+#include "clang/AST/CXXInheritance.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclObjC.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/Type.h"
+#include "clang/AST/TypeLoc.h"
+#include "clang/Basic/SourceLocation.h"
+#include "clang/Basic/Specifiers.h"
+#include "clang/Sema/DeclSpec.h"
+#include "clang/Sema/ScopeInfo.h"
+#include "clang/Sema/Template.h"
+#include "llvm/ADT/ArrayRef.h"
+#include <optional>
+
+namespace clang {
+
+namespace {
+/// Tree transform to "extract" a transformed type from a class template's
+/// constructor to a deduction guide.
+class ExtractTypeForDeductionGuide
+    : public TreeTransform<ExtractTypeForDeductionGuide> {
+  llvm::SmallVectorImpl<TypedefNameDecl *> &MaterializedTypedefs;
+
+public:
+  typedef TreeTransform<ExtractTypeForDeductionGuide> Base;
+  ExtractTypeForDeductionGuide(
+      Sema &SemaRef,
+      llvm::SmallVectorImpl<TypedefNameDecl *> &MaterializedTypedefs)
+      : Base(SemaRef), MaterializedTypedefs(MaterializedTypedefs) {}
+
+  TypeSourceInfo *transform(TypeSourceInfo *TSI) { return TransformType(TSI); }
+
+  QualType TransformTypedefType(TypeLocBuilder &TLB, TypedefTypeLoc TL) {
+    ASTContext &Context = SemaRef.getASTContext();
+    TypedefNameDecl *OrigDecl = TL.getTypedefNameDecl();
+    TypedefNameDecl *Decl = OrigDecl;
+    // Transform the underlying type of the typedef and clone the Decl only if
+    // the typedef has a dependent context.
+    if (OrigDecl->getDeclContext()->isDependentContext()) {
+      TypeLocBuilder InnerTLB;
+      QualType Transformed =
+          TransformType(InnerTLB, OrigDecl->getTypeSourceInfo()->getTypeLoc());
+      TypeSourceInfo *TSI = InnerTLB.getTypeSourceInfo(Context, Transformed);
+      if (isa<TypeAliasDecl>(OrigDecl))
+        Decl = TypeAliasDecl::Create(
+            Context, Context.getTranslationUnitDecl(), OrigDecl->getBeginLoc(),
+            OrigDecl->getLocation(), OrigDecl->getIdentifier(), TSI);
+      else {
+        assert(isa<TypedefDecl>(OrigDecl) && "Not a Type alias or typedef");
+        Decl = TypedefDecl::Create(
+            Context, Context.getTranslationUnitDecl(), OrigDecl->getBeginLoc(),
+            OrigDecl->getLocation(), OrigDecl->getIdentifier(), TSI);
+      }
+      MaterializedTypedefs.push_back(Decl);
+    }
+
+    QualType TDTy = Context.getTypedefType(Decl);
+    TypedefTypeLoc TypedefTL = TLB.push<TypedefTypeLoc>(TDTy);
+    TypedefTL.setNameLoc(TL.getNameLoc());
+
+    return TDTy;
+  }
+};
+} // namespace
+
+ParmVarDecl *transformFunctionTypeParam(
+    Sema &SemaRef, ParmVarDecl *OldParam, DeclContext *DC,
+    MultiLevelTemplateArgumentList &Args,
+    llvm::SmallVectorImpl<TypedefNameDecl *> &MaterializedTypedefs) {
+  TypeSourceInfo *OldDI = OldParam->getTypeSourceInfo();
+  TypeSourceInfo *NewDI;
+  if (auto PackTL = OldDI->getTypeLoc().getAs<PackExpansionTypeLoc>()) {
+    // Expand out the one and only element in each inner pack.
+    Sema::ArgumentPackSubstitutionIndexRAII SubstIndex(SemaRef, 0);
+    NewDI = SemaRef.SubstType(PackTL.getPatternLoc(), Args,
+                              OldParam->getLocation(), OldParam->getDeclName());
+    if (!NewDI)
+      return nullptr;
+    NewDI = SemaRef.CheckPackExpansion(NewDI, PackTL.getEllipsisLoc(),
+                                       PackTL.getTypePtr()->getNumExpansions());
+  } else
+    NewDI = SemaRef.SubstType(OldDI, Args, OldParam->getLocation(),
+                              OldParam->getDeclName());
+  if (!NewDI)
+    return nullptr;
+
+  // Extract the type. This (for instance) replaces references to typedef
+  // members of the current instantiations with the definitions of those
+  // typedefs, avoiding triggering instantiation of the deduced type during
+  // deduction.
+  NewDI = ExtractTypeForDeductionGuide(SemaRef, MaterializedTypedefs)
+              .transform(NewDI);
+
+  // Resolving a wording defect, we also inherit default arguments from the
+  // constructor.
+  ExprResult NewDefArg;
+  if (OldParam->hasDefaultArg()) {
+    // We don't care what the value is (we won't use it); just create a
+    // placeholder to indicate there is a default argument.
+    QualType ParamTy = NewDI->getType();
+    NewDefArg = new (SemaRef.Context)
+        OpaqueValueExpr(OldParam->getDefaultArgRange().getBegin(),
+                        ParamTy.getNonLValueExprType(SemaRef.Context),
+                        ParamTy->isLValueReferenceType()   ? VK_LValue
+                        : ParamTy->isRValueReferenceType() ? VK_XValue
+                                                           : VK_PRValue);
+  }
+  // Handle arrays and functions decay.
+  auto NewType = NewDI->getType();
+  if (NewType->isArrayType() || NewType->isFunctionType())
+    NewType = SemaRef.Context.getDecayedType(NewType);
+
+  ParmVarDecl *NewParam = ParmVarDecl::Create(
+      SemaRef.Context, DC, OldParam->getInnerLocStart(),
+      OldParam->getLocation(), OldParam->getIdentifier(), NewType, NewDI,
+      OldParam->getStorageClass(), NewDefArg.get());
+  NewParam->setScopeInfo(OldParam->getFunctionScopeDepth(),
+                         OldParam->getFunctionScopeIndex());
+  SemaRef.CurrentInstantiationScope->InstantiatedLocal(OldParam, NewParam);
+  return NewParam;
+}
+
+TemplateTypeParmDecl *
+transformTemplateTypeParam(Sema &SemaRef, DeclContext *DC,
+                           TemplateTypeParmDecl *TTP,
+                           MultiLevelTemplateArgumentList &Args,
+                           unsigned NewDepth, unsigned NewIndex) {
+  // TemplateTypeParmDecl's index cannot be changed after creation, so
+  // substitute it directly.
+  auto *NewTTP = TemplateTypeParmDecl::Create(
+      SemaRef.Context, DC, TTP->getBeginLoc(), TTP->getLocation(), NewDepth,
+      NewIndex, TTP->getIdentifier(), TTP->wasDeclaredWithTypename(),
+      TTP->isParameterPack(), TTP->hasTypeConstraint(),
+      TTP->isExpandedParameterPack()
+          ? std::optional<unsigned>(TTP->getNumExpansionParameters())
+          : std::nullopt);
+  if (const auto *TC = TTP->getTypeConstraint())
+    SemaRef.SubstTypeConstraint(NewTTP, TC, Args,
+                                /*EvaluateConstraint*/ true);
+  if (TTP->hasDefaultArgument()) {
+    TypeSourceInfo *InstantiatedDefaultArg =
+        SemaRef.SubstType(TTP->getDefaultArgumentInfo(), Args,
+                          TTP->getDefaultArgumentLoc(), TTP->getDeclName());
+    if (InstantiatedDefaultArg)
+      NewTTP->setDefaultArgument(InstantiatedDefaultArg);
+  }
+  SemaRef.CurrentInstantiationScope->InstantiatedLocal(TTP, NewTTP);
+  return NewTTP;
+}
+
+FunctionTemplateDecl *
+buildDeductionGuide(Sema &SemaRef, TemplateDecl *OriginalTemplate,
+                    TemplateParameterList *TemplateParams,
+                    CXXConstructorDecl *Ctor, ExplicitSpecifier ES,
+                    TypeSourceInfo *TInfo, SourceLocation LocStart,
+                    SourceLocation Loc, SourceLocation LocEnd, bool IsImplicit,
+                    llvm::ArrayRef<TypedefNameDecl *> MaterializedTypedefs) {
+  DeclContext *DC = OriginalTemplate->getDeclContext();
+  auto DeductionGuideName =
+      SemaRef.Context.DeclarationNames.getCXXDeductionGuideName(
+          OriginalTemplate);
+
+  DeclarationNameInfo Name(DeductionGuideName, Loc);
+  ArrayRef<ParmVarDecl *> Params =
+      TInfo->getTypeLoc().castAs<FunctionProtoTypeLoc>().getParams();
+
+  // Build the implicit deduction guide template.
+  auto *Guide =
+      CXXDeductionGuideDecl::Create(SemaRef.Context, DC, LocStart, ES, Name,
+                                    TInfo->getType(), TInfo, LocEnd, Ctor);
+  Guide->setImplicit(IsImplicit);
+  Guide->setParams(Params);
+
+  for (auto *Param : Params)
+    Param->setDeclContext(Guide);
+  for (auto *TD : MaterializedTypedefs)
+    TD->setDeclContext(Guide);
+
+  auto *GuideTemplate = FunctionTemplateDecl::Create(
+      SemaRef.Context, DC, Loc, DeductionGuideName, TemplateParams, Guide);
+  GuideTemplate->setImplicit(IsImplicit);
+  Guide->setDescribedFunctionTemplate(GuideTemplate);
+
+  if (isa<CXXRecordDecl>(DC)) {
+    Guide->setAccess(AS_public);
+    GuideTemplate->setAccess(AS_public);
+  }
+
+  DC->addDecl(GuideTemplate);
+  return GuideTemplate;
+}
+
+} // namespace clang
diff --git a/clang/lib/Sema/CTAD.h b/clang/lib/Sema/CTAD.h
new file mode 100644
index 00000000000000..88110230318f81
--- /dev/null
+++ b/clang/lib/Sema/CTAD.h
@@ -0,0 +1,65 @@
+//===--- CTAD.h - Helper functions for CTAD -------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+//  This file defines helper functions for the class template argument deduction
+//  (CTAD) implementation.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/ASTMutationListener.h"
+#include "clang/AST/ASTStructuralEquivalence.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Type.h"
+#include "clang/Basic/SourceLocation.h"
+#include "clang/Sema/DeclSpec.h"
+#include "clang/Sema/ScopeInfo.h"
+#include "clang/Sema/Template.h"
+#include "llvm/ADT/ArrayRef.h"
+
+namespace clang {
+
+// Transform a given function parameter decl into a deduction guide parameter
+// decl.
+ParmVarDecl *transformFunctionTypeParam(
+    Sema &SemaRef, ParmVarDecl *OldParam, DeclContext *DC,
+    MultiLevelTemplateArgumentList &Args,
+    llvm::SmallVectorImpl<TypedefNameDecl *> &MaterializedTypedefs);
+
+// Transform a given template type parameter into a deduction guide template
+// parameter, rebuilding any internal references to earlier parameters and
+// re-indexing as we go.
+TemplateTypeParmDecl *transformTemplateTypeParam(
+    Sema &SemaRef, DeclContext *DC, TemplateTypeParmDecl *TPT,
+    MultiLevelTemplateArgumentList &Args, unsigned NewDepth, unsigned NewIndex);
+// Similar to above, but for non-type template or template template parameters.
+template <typename NonTypeTemplateOrTemplateTemplateParmDecl>
+NonTypeTemplateOrTemplateTemplateParmDecl *
+transformTemplateParam(Sema &SemaRef, DeclContext *DC,
+                       NonTypeTemplateOrTemplateTemplateParmDecl *OldParam,
+                       MultiLevelTemplateArgumentList &Args,
+                       unsigned NewIndex) {
+  // Ask the template instantiator to do the heavy lifting for us, then adjust
+  // the index of the parameter once it's done.
+  auto *NewParam = cast<NonTypeTemplateOrTemplateTemplateParmDecl>(
+      SemaRef.SubstDecl(OldParam, DC, Args));
+  NewParam->setPosition(NewIndex);
+  return NewParam;
+}
+
+// Build a deduction guide with the specified parameter types.
+FunctionTemplateDecl *buildDeductionGuide(
+    Sema &SemaRef, TemplateDecl *OriginalTemplate,
+    TemplateParameterList *TemplateParams, CXXConstructorDecl *Ctor,
+    ExplicitSpecifier ES, TypeSourceInfo *TInfo, SourceLocation LocStart,
+    SourceLocation Loc, SourceLocation LocEnd, bool IsImplicit,
+    llvm::ArrayRef<TypedefNameDecl *> MaterializedTypedefs = {});
+
+} // namespace clang
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index 457fa377355a97..e6105040af8111 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -10,13 +10,19 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "CTAD.h"
+#include "TypeLocBuilder.h"
 #include "clang/AST/ASTContext.h"
+#include "clang/AST/DeclAccessPair.h"
+#include "clang/AST/DeclBase.h"
+#include "clang/AST/DeclCXX.h"
 #include "clang/AST/DeclObjC.h"
-#include "clang/AST/Expr.h"
 #include "clang/AST/ExprCXX.h"
 #include "clang/AST/ExprObjC.h"
 #include "clang/AST/ExprOpenMP.h"
 #include "clang/AST/IgnoreExpr.h"
+#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/AST/Type.h"
 #include "clang/AST/TypeLoc.h"
 #include "clang/Basic/CharInfo.h"
 #include "clang/Basic/SourceManager.h"
@@ -28,7 +34,9 @@
 #include "clang/Sema/Lookup.h"
 #include "clang/Sema/Ownership.h"
 #include "clang/Sema/SemaInternal.h"
+#include "clang/Sema/Template.h"
 #include "llvm/ADT/APInt.h"
+#include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/FoldingSet.h"
 #include "llvm/ADT/PointerIntPair.h"
 #include "llvm/ADT/SmallString.h"
@@ -10583,6 +10591,213 @@ static bool isOrIsDerivedFromSpecializationOf(CXXRecordDecl *RD,
   return !(NotSpecialization(RD) && RD->forallBases(NotSpecialization));
 }
 
+// Transform to form a corresponding deduction guide for type alias template
+// decl.
+//
+// This class implements the C++ [over.match.class.deduct]p3:
+//   ... Let g denote the result of substituting these deductions into f. If
+//   substitution succeeds, form a function or function template f' with the
+//   following properties and add it to the set of guides of A...
+class AliasTemplateDeductionGuideTransform {
+public:
+  AliasTemplateDeductionGuideTransform(Sema &S, TypeAliasTemplateDecl *Alias)
+      : SemaRef(S), AliasTemplate(Alias), DC(Alias->getDeclContext()) {}
+  // Returns the result of substituting the deduced template arguments into F.
+  NamedDecl *transform(CXXDeductionGuideDecl *F,
+                       ArrayRef<TemplateArgument> DeducedArgs,
+                       ArrayRef<NamedDecl *> NonDeducedTemplateParamsInF) {
+    // Template parameters of the f'.
+    //
+    // C++ [over.match.class.deduct]p3.2:
+    //   If f is a function template, f' is a function template whose template
+    //   parameter list consists of all the template parameters of A (including
+    //   their default template arguments) that appear in the above deductions
+    //   or (recursively) in their default template arguments
+    SmallVector<NamedDecl *> TemplateParamsInFPrime =
+        FindAppearedTemplateParamsInAlias(DeducedArgs);
+    //   ...followed by the template parameters of f that were not deduced
+    //   (including their default template arguments)
+    TemplateParamsInFPrime.append(NonDeducedTemplateParamsInF.begin(),
+                                  NonDeducedTemplateParamsInF.end());
+
+    LocalInstantiationScope Scope(SemaRef);
+    SmallVector<TemplateArgument, 16> Depth1Args;
+    SmallVector<NamedDecl *, 16> AllParams;
+    SmallVector<TemplateArgument, 16> SubstArgs;
+    unsigned TemplateParamIndex = 0;
+    TemplateParameterList *TemplateParams = nullptr;
+
+    for (NamedDecl *Param : TemplateParamsInFPrime) {
+      MultiLevelTemplateArgumentList Args;
+
+      Args.setKind(TemplateSubstitutionKind::Rewrite);
+      Args.addOuterTemplateArguments(Depth1Args);
+      Args.addOuterRetainedLevel();
+      NamedDecl *NewParam =
+          transformTemplateParameter(Param, Args, TemplateParamIndex++);
+      if (!NewParam) {
+        llvm::errs() << "Faile to generate new param!\n";
+        return nullptr;
+      }
+      auto NewArgumentForNewParam =
+          SemaRef.Context.getCanonicalTemplateArgument(
+              SemaRef.Context.getInjectedTemplateArg(NewParam));
+      Depth1Args.push_back(NewArgumentForNewParam);
+      AllParams.push_back(NewParam);
+      SubstArgs.push_back(NewArgumentForNewParam);
+    }
+    // FIXME: substitute new template parameters into the requires-clause.
+    TemplateParams = TemplateParameterList::Create(
+        SemaRef.Context,
+        AliasTemplate->getTemplateParameters()->getTemplateLoc(),
+        AliasTemplate->getTemplateParameters()->getLAngleLoc(), AllParams,
+        AliasTemplate->getTemplateParameters()->getRAngleLoc(),
+        /*RequiresClause=*/nullptr);
+
+    MultiLevelTemplateArgumentList Args;
+    Args.setKind(TemplateSubstitutionKind::Rewrite);
+    Args.addOuterTemplateArguments(SubstArgs);
+    Args.addOuterRetainedLevel();
+
+    FunctionProtoTypeLoc FPTL = F->getTypeSourceInfo()
+                                    ->getTypeLoc()
+                                    .getAsAdjusted<FunctionProtoTypeLoc>();
+    assert(FPTL && "no prototype for underlying deduction guides");
+
+    // Transform the type of the function, adjusting the return type and
+    // replacing references to the old parameters with references to the
+    // new ones.
+    TypeLocBuilder TLB;
+    SmallVector<ParmVarDecl *, 8> Params;
+    SmallVector<TypedefNameDecl *, 4> MaterializedTypedefs;
+    QualType NewType = transformFunctionProtoType(
+        TLB, FPTL, Params, Args, F->getReturnType(), MaterializedTypedefs);
+    if (NewType.isNull())
+      return nullptr;
+    TypeSourceInfo *NewTInfo = TLB.getTypeSourceInfo(SemaRef.Context, NewType);
+
+    return clang::buildDeductionGuide(
+        SemaRef, AliasTemplate, TemplateParams,
+        F->getCorrespondingConstructor(), F->getExplicitSpecifier(), NewTInfo,
+        AliasTemplate->getBeginLoc(), AliasTemplate->getLocation(),
+        AliasTemplate->getEndLoc(), F->isImplicit(), MaterializedTypedefs);
+  }
+
+private:
+  // Find all template parameters of the AliasTemplate that appear in the
+  // DeducedArgs.
+  SmallVector<NamedDecl *>
+  FindAppearedTemplateParamsInAlias(ArrayRef<TemplateArgument> DeducedArgs) {
+    struct FindAppearedTemplateParams
+        : public RecursiveASTVisitor<FindAppearedTemplateParams> {
+      llvm::DenseSet<NamedDecl *> TemplateParamsInAlias...
[truncated]

@cor3ntin
Copy link
Contributor

@hokein @sam-mccall to repeat what was said in the language group last week.

We do not think a feature flag is a good fit.

  • Until clang 19 ships (in 6 months) we do not need to stabilize the feature (but ofc we should avoid regressions, which we would not find if hidden behind a flag), and the flag does not guarantee existing features are not impacted.

And if we break trunk, we revert, you get extra test cases... it's perfectly fine

  • The feature should be behind a -std=C++20 flag.
  • We should not update __cpp_deduction_guides until the feature is battle tested

We do that for about every feature that is not concept/module scale (and then again, these things were tses), and this isn't big enough to warrant a novel approach.

I still think we should land this soon and we can start a review whenever you are ready.
If you want to land it in an incomplete state, we just need to make sure we have a good collective understanding of the remaining work that would need to be done to bring the feature to completion (tests with fix me, issues, etc)

Does that make sense to you?

@hokein
Copy link
Collaborator Author

hokein commented Jan 29, 2024

Thanks for the summary.

We do not think a feature flag is a good fit.

  • Until clang 19 ships (in 6 months) we do not need to stabilize the feature (but ofc we should avoid regressions, which we would not find if hidden behind a flag), and the flag does not guarantee existing features are not impacted.

I think this works fine with clang open-source release cycles (18 release was just cut, we have 6 months from now to polish this feature). However, this is not applicable for our internal case. At Google, we have used C++20 in production, and we have our own toolchain release cycles (which stay close to upstream main). Checking in an in-development C++20 feature without a proper safeguard poses risks to users (clang crashes, incorrect compilations, and other issues).

So having a temporary flag is useful, it enables us to incrementally test and experiment it without compromising the experience for most users.

And if we break trunk, we revert, you get extra test cases... it's perfectly fine

We could revert the whole patch when things are broken (and it is what we usually do). But I'm not sure revert/reland a big patch back and forth is a good idea.

This also brings a general question: what level of quality should we require from experimental features in HEAD?

We do that for about every feature that is not concept/module scale (and then again, these things were tses), and this isn't big enough to warrant a novel approach.

Yeah, I agree that this is a narrow feature. Given that this feature is very late to the party, and C++20 has been used in some productions, I think it is probably reasonable to have a flag.

I still think we should land this soon and we can start a review whenever you are ready.
If you want to land it in an incomplete state, we just need to make sure we have a good collective understanding of the remaining work that would need to be done to bring the feature to completion (tests with fix me, issues, etc)

+1, I think the current state should be good enough and ready for review. The remaining big piece (see the FIXME) is to implement the associated constraints (over.match.class.deduct#3.3).

@hokein
Copy link
Collaborator Author

hokein commented Jan 29, 2024

Copy link
Collaborator

@shafik shafik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to aid the discussion on the temporary flag, I think it would be helpful to have a full set of tests that show what elements won't work right with the current set of changes.

I think this will help us gauge how impactful partial support would be and maybe help guide the discussion on alternatives.

@erichkeane erichkeane self-requested a review January 29, 2024 16:35
@erichkeane
Copy link
Collaborator

We could revert the whole patch when things are broken (and it is what we usually do). But I'm not sure revert/reland a big patch back and forth is a good idea.

This is effectively what we did repeatedly with the deferred concepts patch and IMO, was a really nice healthy way of doing it. If you organize your commits (much easier on github than it was on Phab), you can present JUST the changes between revisions/fixes and get a really nice revert/fix/release cycle going.

I'd encourage just enabling by default (to avoid losing the test breadth that comes along with it), and reverting if there are sufficiently 'bad' breakages.

Note that you'd not have to revert for all 'bugs', just those that break existing code. We can hold off on setting the Feature Test Macro until we're comfortable with it though.

@@ -10636,6 +10889,113 @@ QualType Sema::DeduceTemplateSpecializationFromInitializer(
// clear on this, but they're not found by name so access does not apply.
Guides.suppressDiagnostics();

SmallVector<DeclAccessPair> GuidesCandidates;
if (AliasTemplate) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering if we need to separate the code that tweaks the candidate set into another function.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, and this is not the proper place to construct deduction guides for alias templates (they should live in DeclareImplicitDeductionGuides).

@hokein hokein force-pushed the ctad-alias-template-draft branch from becb1bd to 05740ec Compare February 20, 2024 14:19
@hokein
Copy link
Collaborator Author

hokein commented Feb 20, 2024

This patch is ready for review now.

Copy link
Contributor

@cor3ntin cor3ntin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great generally.

I did leave a few preliminary comments.
The alias ctad mechanism is not easy to get my head around so this is going to take a few rounds

Thanks!

Comment on lines 10540 to 10541
CodeSynthesisContext::SynthesisKind CSC =
CodeSynthesisContext::ExplicitTemplateArgumentSubstitution);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain the default value here? If there was a default at all, one would think it would be TemplateInstantiation

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a refactoring and non-functional change, before this patch, we always use the CodeSynthesisContext::ExplicitTemplateArgumentSubstitution in this API implementation, the change here is to promote it to a function parameter (making a default value so that we don't need to change all existing callsites)

Comment on lines 2814 to 2817
F->getTemplateParameters()->size());
// FIXME: DeduceTemplateArguments stops immediately at the first
// non-deducible template parameter, extend it to continue performing
// deduction for rest of parameters.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have a test case for that?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem to cause any issues after trying a bunch cases.

The major testcase in my mind was using AFoo = Foo<double, int> with Foo<IsInt X, typename Y>, DeduceTemplateArguments will stop at the first argument -- we can't deduce X from double as double doesn't satisfy the IsInt constraint. But my understanding was incorrect, DeduceTemplateArguments still deduce the template arguments successfully for this case (X -> double, Y -> int). The concept constraint check is performed in other places (added this case in the unittest).

the other one is the case where template argument type mismatches template parameter type, e.g. template<typename T> class Foo; template<int N> using AFoo = Foo<N>;, this has been diagnosed when parsing the using alias.

I think we're fine here (and the FIXME is still valid).

Comment on lines +2862 to +2874
if (auto *TTP = dyn_cast<TemplateTypeParmDecl>(TemplateParam))
return transformTemplateTypeParam(SemaRef, DC, TTP, Args,
TTP->getDepth(), NewIndex);
if (auto *TTP = dyn_cast<TemplateTemplateParmDecl>(TemplateParam))
return transformTemplateParam(SemaRef, DC, TTP, Args, NewIndex,
TTP->getDepth());
if (auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(TemplateParam))
return transformTemplateParam(SemaRef, DC, NTTP, Args, NewIndex,
NTTP->getDepth());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we put that in a function?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This lambda is only used inside this function (called twice below), I think it is better to keep it local (there is a similar variant transformTemplateParameter function in this file as well, I'd avoid adding one more)

@hokein hokein force-pushed the ctad-alias-template-draft branch from caf055b to 59e1263 Compare February 23, 2024 14:10
Copy link
Collaborator Author

@hokein hokein left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the initial review.

Comment on lines 10540 to 10541
CodeSynthesisContext::SynthesisKind CSC =
CodeSynthesisContext::ExplicitTemplateArgumentSubstitution);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a refactoring and non-functional change, before this patch, we always use the CodeSynthesisContext::ExplicitTemplateArgumentSubstitution in this API implementation, the change here is to promote it to a function parameter (making a default value so that we don't need to change all existing callsites)

// Find all template parameters of the AliasTemplate that appear in the
// given DeducedArgs.
SmallVector<unsigned>
FindAppearedTemplateParamsInAlias(ArrayRef<TemplateArgument> DeducedArgs,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm leaning towards Appeared to align with the C++ standard. It basically does "whose template parameter list consists of all the template parameters of A (including their default template arguments) that appear in the above deductions"

Comment on lines 2814 to 2817
F->getTemplateParameters()->size());
// FIXME: DeduceTemplateArguments stops immediately at the first
// non-deducible template parameter, extend it to continue performing
// deduction for rest of parameters.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem to cause any issues after trying a bunch cases.

The major testcase in my mind was using AFoo = Foo<double, int> with Foo<IsInt X, typename Y>, DeduceTemplateArguments will stop at the first argument -- we can't deduce X from double as double doesn't satisfy the IsInt constraint. But my understanding was incorrect, DeduceTemplateArguments still deduce the template arguments successfully for this case (X -> double, Y -> int). The concept constraint check is performed in other places (added this case in the unittest).

the other one is the case where template argument type mismatches template parameter type, e.g. template<typename T> class Foo; template<int N> using AFoo = Foo<N>;, this has been diagnosed when parsing the using alias.

I think we're fine here (and the FIXME is still valid).

Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy when Corentin is, so he can approve when he's ready.

hokein added 11 commits March 6, 2024 16:00
…lias

templates P1814R0.

This patch implements the C++20 feature -- CTAD for alias templates.
This is an initial patch, which covers most of pieces, the major missing
piece is to implement the associated constraints (over.match.class.deduct#3.3)
for the synthesized deduction guides (we can address in a followup).

This patch also refactors the existing `ConvertConstructorToDeductionGuideTransform`
to allow code reuse.
- Move the implementation to SemaTemplate.cpp where the typical CTAD
  implementation lives, thus no sepatate CTAD.h/.cpp;
- Rewrite the implementation, we leverage more on clang's template
  instantatiation mechanism to build the deduction guide, it
  implementation is simplier;
- Some enhancements on TreeTransform and TemplateInstantiator to allow
  running on BuildingDeductionGuides mode;
- Added more tests;
- simplify the if
- add a release note
@cor3ntin
Copy link
Contributor

cor3ntin commented Mar 6, 2024

Can you mark the status of the paper in cxx_status.html as partial (with a <details></details> tag explaining what is missing)?
Can you update the commit message / changelog to refer to the paper number?

Thanks

- update err_deduced_non_class_template_specialization_type to include alias templates
- update cxx_status.html
- fix a typo in ReleaseNotes
@hokein hokein force-pushed the ctad-alias-template-draft branch from dc15d1f to 57c5b98 Compare March 7, 2024 08:46
Copy link
Contributor

@Endilll Endilll left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sema.h changes look good.

Copy link
Contributor

@cor3ntin cor3ntin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM
Thanks for working on that!

@hokein
Copy link
Collaborator Author

hokein commented Mar 7, 2024

Thank you for all the reviews, I will wait 1 or 2 days before merging it in case anyone has more comments.

@cor3ntin
Copy link
Contributor

cor3ntin commented Mar 7, 2024

Thank you for all the reviews, I will wait 1 or 2 days before merging it in case anyone has more comments.

Sure!
Do you plan to keep working on completing this feature?

@hokein
Copy link
Collaborator Author

hokein commented Mar 8, 2024

Thank you for all the reviews, I will wait 1 or 2 days before merging it in case anyone has more comments.

Sure! Do you plan to keep working on completing this feature?

Yeah, I plan to complete this feature before the next clang release. If anyone is willing to help, that would be even greater.

BTW, should we close #54051 with this PR? or should we keep it open until the feature is fully implemented? The is_deducible part is a major missing piece, I think we can close #54051 and open a separate issue to track the missing piece, WDYT?

@cor3ntin
Copy link
Contributor

cor3ntin commented Mar 8, 2024

BTW, should we close #54051 with this PR? or should we keep it open until the feature is fully implemented? The is_deducible part is a major missing piece, I think we can close #54051 and open a separate issue to track the missing piece, WDYT?

Sounds good

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++20 clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Clang C++20 Feature: P1814R0 - Wording for Class Template Argument Deduction for Alias Templates
8 participants