diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 942820a526857..01cec807f75eb 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -91,6 +91,10 @@ C++20 Feature Support current module units. Fixes `#84002 `_. +- Initial support for class template argument deduction (CTAD) for type alias + templates (`P1814R0 `_). + (#GH54051). + C++23 Feature Support ^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 5a90e631a894c..45b862971a291 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -2515,10 +2515,11 @@ def err_deduced_class_template_compound_type : Error< "cannot %select{form pointer to|form reference to|form array of|" "form function returning|use parentheses when declaring variable with}0 " "deduced class template specialization type">; -def err_deduced_non_class_template_specialization_type : Error< +def err_deduced_non_class_or_alias_template_specialization_type : Error< "%select{|function template|variable template|alias template|" "template template parameter|concept|template}0 %1 requires template " - "arguments; argument deduction only allowed for class templates">; + "arguments; argument deduction only allowed for class templates or alias " + "templates">; def err_deduced_class_template_ctor_ambiguous : Error< "ambiguous deduction for template arguments of %0">; def err_deduced_class_template_ctor_no_viable : Error< @@ -8199,6 +8200,11 @@ let CategoryName = "Lambda Issue" in { def warn_cxx17_compat_lambda_def_ctor_assign : Warning< "%select{default construction|assignment}0 of lambda is incompatible with " "C++ standards before C++20">, InGroup, DefaultIgnore; + + // C++20 class template argument deduction for alias templates. + def warn_cxx17_compat_ctad_for_alias_templates : Warning< + "class template argument deduction for alias templates is incompatible with " + "C++ standards before C++20">, InGroup, DefaultIgnore; } def err_return_in_captured_stmt : Error< diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 2d949f3fc9a71..bfb40816fdb57 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -9826,6 +9826,12 @@ class Sema final { ArrayRef TemplateArgs, sema::TemplateDeductionInfo &Info); + TemplateDeductionResult DeduceTemplateArguments( + TemplateParameterList *TemplateParams, ArrayRef Ps, + ArrayRef As, sema::TemplateDeductionInfo &Info, + SmallVectorImpl &Deduced, + bool NumberOfArgumentsMustMatch); + TemplateDeductionResult SubstituteExplicitTemplateArguments( FunctionTemplateDecl *FunctionTemplate, TemplateArgumentListInfo &ExplicitTemplateArgs, @@ -10377,6 +10383,9 @@ class Sema final { InstantiatingTemplate &operator=(const InstantiatingTemplate &) = delete; }; + bool SubstTemplateArgument(const TemplateArgumentLoc &Input, + const MultiLevelTemplateArgumentList &TemplateArgs, + TemplateArgumentLoc &Output); bool SubstTemplateArguments(ArrayRef Args, const MultiLevelTemplateArgumentList &TemplateArgs, @@ -10861,9 +10870,11 @@ class Sema final { ParmVarDecl *Param); void InstantiateExceptionSpec(SourceLocation PointOfInstantiation, FunctionDecl *Function); - 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/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp index 011deed7a9a9f..aa470adb30b47 100644 --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -10720,13 +10720,40 @@ QualType Sema::DeduceTemplateSpecializationFromInitializer( if (TemplateName.isDependent()) return SubstAutoTypeDependent(TSInfo->getType()); - // We can only perform deduction for class templates. + // We can only perform deduction for class templates or alias templates. auto *Template = dyn_cast_or_null(TemplateName.getAsTemplateDecl()); + TemplateDecl *LookupTemplateDecl = Template; + if (!Template) { + if (auto *AliasTemplate = dyn_cast_or_null( + TemplateName.getAsTemplateDecl())) { + Diag(Kind.getLocation(), + diag::warn_cxx17_compat_ctad_for_alias_templates); + LookupTemplateDecl = AliasTemplate; + auto UnderlyingType = AliasTemplate->getTemplatedDecl() + ->getUnderlyingType() + .getCanonicalType(); + // C++ [over.match.class.deduct#3]: ..., the defining-type-id of A must be + // of the form + // [typename] [nested-name-specifier] [template] simple-template-id + if (const auto *TST = + UnderlyingType->getAs()) { + Template = dyn_cast_or_null( + TST->getTemplateName().getAsTemplateDecl()); + } else if (const auto *RT = UnderlyingType->getAs()) { + // Cases where template arguments in the RHS of the alias are not + // dependent. e.g. + // using AliasFoo = Foo; + if (const auto *CTSD = llvm::dyn_cast( + RT->getAsCXXRecordDecl())) + Template = CTSD->getSpecializedTemplate(); + } + } + } if (!Template) { Diag(Kind.getLocation(), - diag::err_deduced_non_class_template_specialization_type) - << (int)getTemplateNameKindForDiagnostics(TemplateName) << TemplateName; + diag::err_deduced_non_class_or_alias_template_specialization_type) + << (int)getTemplateNameKindForDiagnostics(TemplateName) << TemplateName; if (auto *TD = TemplateName.getAsTemplateDecl()) NoteTemplateLocation(*TD); return QualType(); @@ -10753,10 +10780,10 @@ QualType Sema::DeduceTemplateSpecializationFromInitializer( // template-name, a function template [...] // - For each deduction-guide, a function or function template [...] DeclarationNameInfo NameInfo( - Context.DeclarationNames.getCXXDeductionGuideName(Template), + Context.DeclarationNames.getCXXDeductionGuideName(LookupTemplateDecl), TSInfo->getTypeLoc().getEndLoc()); LookupResult Guides(*this, NameInfo, LookupOrdinaryName); - LookupQualifiedName(Guides, Template->getDeclContext()); + LookupQualifiedName(Guides, LookupTemplateDecl->getDeclContext()); // FIXME: Do not diagnose inaccessible deduction guides. The standard isn't // clear on this, but they're not found by name so access does not apply. diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp index 7e91815c2d52a..56b6bfee3c66c 100644 --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/lib/Sema/SemaTemplate.cpp @@ -2264,6 +2264,92 @@ class ExtractTypeForDeductionGuide } }; +// 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 MaterializedTypedefs = {}) { + DeclContext *DC = OriginalTemplate->getDeclContext(); + auto DeductionGuideName = + SemaRef.Context.DeclarationNames.getCXXDeductionGuideName( + OriginalTemplate); + + DeclarationNameInfo Name(DeductionGuideName, Loc); + ArrayRef Params = + TInfo->getTypeLoc().castAs().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(DC)) { + Guide->setAccess(AS_public); + GuideTemplate->setAccess(AS_public); + } + + DC->addDecl(GuideTemplate); + return GuideTemplate; +} + +// Transform a given template type parameter `TTP`. +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(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; +} +// Similar to above, but for non-type template or template template parameters. +template +NonTypeTemplateOrTemplateTemplateParmDecl * +transformTemplateParam(Sema &SemaRef, DeclContext *DC, + NonTypeTemplateOrTemplateTemplateParmDecl *OldParam, + MultiLevelTemplateArgumentList &Args, unsigned NewIndex, + unsigned NewDepth) { + // 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( + SemaRef.SubstDecl(OldParam, DC, Args)); + NewParam->setPosition(NewIndex); + NewParam->setDepth(NewDepth); + return NewParam; +} + /// Transform to convert portions of a constructor declaration into the /// corresponding deduction guide, per C++1z [over.match.class.deduct]p1. struct ConvertConstructorToDeductionGuideTransform { @@ -2340,7 +2426,6 @@ struct ConvertConstructorToDeductionGuideTransform { NamedDecl *NewParam = transformTemplateParameter(Param, Args); if (!NewParam) return nullptr; - // Constraints require that we substitute depth-1 arguments // to match depths when substituted for evaluation later Depth1Args.push_back(SemaRef.Context.getCanonicalTemplateArgument( @@ -2413,9 +2498,10 @@ struct ConvertConstructorToDeductionGuideTransform { return nullptr; TypeSourceInfo *NewTInfo = TLB.getTypeSourceInfo(SemaRef.Context, NewType); - return buildDeductionGuide(TemplateParams, CD, CD->getExplicitSpecifier(), - NewTInfo, CD->getBeginLoc(), CD->getLocation(), - CD->getEndLoc(), MaterializedTypedefs); + return buildDeductionGuide( + SemaRef, Template, TemplateParams, CD, CD->getExplicitSpecifier(), + NewTInfo, CD->getBeginLoc(), CD->getLocation(), CD->getEndLoc(), + /*IsImplicit=*/true, MaterializedTypedefs); } /// Build a deduction guide with the specified parameter types. @@ -2450,8 +2536,9 @@ struct ConvertConstructorToDeductionGuideTransform { Params.push_back(NewParam); } - return buildDeductionGuide(GetTemplateParameterList(Template), nullptr, - ExplicitSpecifier(), TSI, Loc, Loc, Loc); + return buildDeductionGuide( + SemaRef, Template, GetTemplateParameterList(Template), nullptr, + ExplicitSpecifier(), TSI, Loc, Loc, Loc, /*IsImplicit=*/true); } private: @@ -2460,50 +2547,18 @@ struct ConvertConstructorToDeductionGuideTransform { /// renumbering as we go. NamedDecl *transformTemplateParameter(NamedDecl *TemplateParam, MultiLevelTemplateArgumentList &Args) { - if (auto *TTP = dyn_cast(TemplateParam)) { - // TemplateTypeParmDecl's index cannot be changed after creation, so - // substitute it directly. - auto *NewTTP = TemplateTypeParmDecl::Create( - SemaRef.Context, DC, TTP->getBeginLoc(), TTP->getLocation(), - TTP->getDepth() - 1, Depth1IndexAdjustment + TTP->getIndex(), - TTP->getIdentifier(), TTP->wasDeclaredWithTypename(), - TTP->isParameterPack(), TTP->hasTypeConstraint(), - TTP->isExpandedParameterPack() - ? std::optional(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(TemplateParam, - NewTTP); - return NewTTP; - } - + if (auto *TTP = dyn_cast(TemplateParam)) + return transformTemplateTypeParam( + SemaRef, DC, TTP, Args, TTP->getDepth() - 1, + Depth1IndexAdjustment + TTP->getIndex()); if (auto *TTP = dyn_cast(TemplateParam)) - return transformTemplateParameterImpl(TTP, Args); - - return transformTemplateParameterImpl( - cast(TemplateParam), Args); - } - template - TemplateParmDecl * - transformTemplateParameterImpl(TemplateParmDecl *OldParam, - MultiLevelTemplateArgumentList &Args) { - // 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(SemaRef.SubstDecl(OldParam, DC, Args)); - assert(NewParam->getDepth() == OldParam->getDepth() - 1 && - "unexpected template param depth"); - NewParam->setPosition(NewParam->getPosition() + Depth1IndexAdjustment); - return NewParam; + return transformTemplateParam(SemaRef, DC, TTP, Args, + Depth1IndexAdjustment + TTP->getIndex(), + TTP->getDepth() - 1); + auto *NTTP = cast(TemplateParam); + return transformTemplateParam(SemaRef, DC, NTTP, Args, + Depth1IndexAdjustment + NTTP->getIndex(), + NTTP->getDepth() - 1); } QualType transformFunctionProtoType( @@ -2598,7 +2653,7 @@ struct ConvertConstructorToDeductionGuideTransform { // placeholder to indicate there is a default argument. QualType ParamTy = NewDI->getType(); NewDefArg = new (SemaRef.Context) - OpaqueValueExpr(OldParam->getDefaultArgRange().getBegin(), + OpaqueValueExpr(OldParam->getDefaultArg()->getBeginLoc(), ParamTy.getNonLValueExprType(SemaRef.Context), ParamTy->isLValueReferenceType() ? VK_LValue : ParamTy->isRValueReferenceType() ? VK_XValue @@ -2618,44 +2673,310 @@ struct ConvertConstructorToDeductionGuideTransform { SemaRef.CurrentInstantiationScope->InstantiatedLocal(OldParam, NewParam); return NewParam; } +}; + +// Find all template parameters that appear in the given DeducedArgs. +// Return the indices of the template parameters in the TemplateParams. +SmallVector TemplateParamsReferencedInTemplateArgumentList( + ArrayRef TemplateParams, + ArrayRef DeducedArgs) { + struct TemplateParamsReferencedFinder + : public RecursiveASTVisitor { + llvm::DenseSet TemplateParams; + llvm::DenseSet ReferencedTemplateParams; + + TemplateParamsReferencedFinder(ArrayRef TemplateParams) + : TemplateParams(TemplateParams.begin(), TemplateParams.end()) {} + + bool VisitTemplateTypeParmType(TemplateTypeParmType *TTP) { + TTP->getIndex(); + MarkAppeared(TTP->getDecl()); + return true; + } + bool VisitDeclRefExpr(DeclRefExpr *DRE) { + MarkAppeared(DRE->getFoundDecl()); + return true; + } + + void MarkAppeared(NamedDecl *ND) { + if (TemplateParams.contains(ND)) + ReferencedTemplateParams.insert(ND); + } + }; + TemplateParamsReferencedFinder Finder(TemplateParams); + Finder.TraverseTemplateArguments(DeducedArgs); - FunctionTemplateDecl *buildDeductionGuide( - TemplateParameterList *TemplateParams, CXXConstructorDecl *Ctor, - ExplicitSpecifier ES, TypeSourceInfo *TInfo, SourceLocation LocStart, - SourceLocation Loc, SourceLocation LocEnd, - llvm::ArrayRef MaterializedTypedefs = {}) { - DeclarationNameInfo Name(DeductionGuideName, Loc); - ArrayRef Params = - TInfo->getTypeLoc().castAs().getParams(); + SmallVector Results; + for (unsigned Index = 0; Index < TemplateParams.size(); ++Index) { + if (Finder.ReferencedTemplateParams.contains(TemplateParams[Index])) + Results.push_back(Index); + } + return Results; +} - // Build the implicit deduction guide template. - auto *Guide = - CXXDeductionGuideDecl::Create(SemaRef.Context, DC, LocStart, ES, Name, - TInfo->getType(), TInfo, LocEnd, Ctor); - Guide->setImplicit(); - Guide->setParams(Params); +bool hasDeclaredDeductionGuides(DeclarationName Name, DeclContext *DC) { + // Check whether we've already declared deduction guides for this template. + // FIXME: Consider storing a flag on the template to indicate this. + assert(Name.getNameKind() == + DeclarationName::NameKind::CXXDeductionGuideName && + "name must be a deduction guide name"); + auto Existing = DC->lookup(Name); + for (auto *D : Existing) + if (D->isImplicit()) + return true; + return false; +} - for (auto *Param : Params) - Param->setDeclContext(Guide); - for (auto *TD : MaterializedTypedefs) - TD->setDeclContext(Guide); +// Build deduction guides for a type alias template. +void DeclareImplicitDeductionGuidesForTypeAlias( + Sema &SemaRef, TypeAliasTemplateDecl *AliasTemplate, SourceLocation Loc) { + auto &Context = SemaRef.Context; + // FIXME: if there is an explicit deduction guide after the first use of the + // type alias usage, we will not cover this explicit deduction guide. fix this + // case. + if (hasDeclaredDeductionGuides( + Context.DeclarationNames.getCXXDeductionGuideName(AliasTemplate), + AliasTemplate->getDeclContext())) + return; + // Unwrap the sugared ElaboratedType. + auto RhsType = AliasTemplate->getTemplatedDecl() + ->getUnderlyingType() + .getSingleStepDesugaredType(Context); + TemplateDecl *Template = nullptr; + llvm::ArrayRef AliasRhsTemplateArgs; + if (const auto *TST = RhsType->getAs()) { + // Cases where the RHS of the alias is dependent. e.g. + // template + // using AliasFoo1 = Foo; // a class/type alias template specialization + Template = TST->getTemplateName().getAsTemplateDecl(); + AliasRhsTemplateArgs = TST->template_arguments(); + } else if (const auto *RT = RhsType->getAs()) { + // Cases where template arguments in the RHS of the alias are not + // dependent. e.g. + // using AliasFoo = Foo; + if (const auto *CTSD = llvm::dyn_cast( + RT->getAsCXXRecordDecl())) { + Template = CTSD->getSpecializedTemplate(); + AliasRhsTemplateArgs = CTSD->getTemplateArgs().asArray(); + } + } else { + assert(false && "unhandled RHS type of the alias"); + } + if (!Template) + return; + DeclarationNameInfo NameInfo( + Context.DeclarationNames.getCXXDeductionGuideName(Template), Loc); + LookupResult Guides(SemaRef, NameInfo, clang::Sema::LookupOrdinaryName); + SemaRef.LookupQualifiedName(Guides, Template->getDeclContext()); + Guides.suppressDiagnostics(); + + for (auto *G : Guides) { + FunctionTemplateDecl *F = dyn_cast(G); + if (!F) + continue; + auto RType = F->getTemplatedDecl()->getReturnType(); + // The (trailing) return type of the deduction guide. + const TemplateSpecializationType *FReturnType = + RType->getAs(); + if (const auto *InjectedCNT = RType->getAs()) + // implicitly-generated deduction guide. + FReturnType = InjectedCNT->getInjectedTST(); + else if (const auto *ET = RType->getAs()) + // explicit deduction guide. + FReturnType = ET->getNamedType()->getAs(); + assert(FReturnType && "expected to see a return type"); + // Deduce template arguments of the deduction guide f from the RHS of + // the alias. + // + // C++ [over.match.class.deduct]p3: ...For each function or function + // template f in the guides of the template named by the + // simple-template-id of the defining-type-id, the template arguments + // of the return type of f are deduced from the defining-type-id of A + // according to the process in [temp.deduct.type] with the exception + // that deduction does not fail if not all template arguments are + // deduced. + // + // + // template + // f(X, Y) -> f; + // + // template + // using alias = f; + // + // The RHS of alias is f, we deduced the template arguments of + // the return type of the deduction guide from it: Y->int, X->U + sema::TemplateDeductionInfo TDeduceInfo(Loc); + // Must initialize n elements, this is required by DeduceTemplateArguments. + SmallVector DeduceResults( + F->getTemplateParameters()->size()); + + // FIXME: DeduceTemplateArguments stops immediately at the first + // non-deducible template argument. However, this doesn't seem to casue + // issues for practice cases, we probably need to extend it to continue + // performing deduction for rest of arguments to align with the C++ + // standard. + SemaRef.DeduceTemplateArguments( + F->getTemplateParameters(), FReturnType->template_arguments(), + AliasRhsTemplateArgs, TDeduceInfo, DeduceResults, + /*NumberOfArgumentsMustMatch=*/false); + + SmallVector DeducedArgs; + SmallVector NonDeducedTemplateParamsInFIndex; + // !!NOTE: DeduceResults respects the sequence of template parameters of + // the deduction guide f. + for (unsigned Index = 0; Index < DeduceResults.size(); ++Index) { + if (const auto &D = DeduceResults[Index]; !D.isNull()) // Deduced + DeducedArgs.push_back(D); + else + NonDeducedTemplateParamsInFIndex.push_back(Index); + } + auto DeducedAliasTemplateParams = + TemplateParamsReferencedInTemplateArgumentList( + AliasTemplate->getTemplateParameters()->asArray(), DeducedArgs); + // All template arguments null by default. + SmallVector TemplateArgsForBuildingFPrime( + F->getTemplateParameters()->size()); + + Sema::InstantiatingTemplate BuildingDeductionGuides( + SemaRef, AliasTemplate->getLocation(), F, + Sema::InstantiatingTemplate::BuildingDeductionGuidesTag{}); + if (BuildingDeductionGuides.isInvalid()) + return; + LocalInstantiationScope Scope(SemaRef); - auto *GuideTemplate = FunctionTemplateDecl::Create( - SemaRef.Context, DC, Loc, DeductionGuideName, TemplateParams, Guide); - GuideTemplate->setImplicit(); - Guide->setDescribedFunctionTemplate(GuideTemplate); + // Create a template parameter list for the synthesized deduction guide 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 FPrimeTemplateParams; + // Store template arguments that refer to the newly-created template + // parameters, used for building `TemplateArgsForBuildingFPrime`. + SmallVector TransformedDeducedAliasArgs( + AliasTemplate->getTemplateParameters()->size()); + auto TransformTemplateParameter = + [&SemaRef](DeclContext *DC, NamedDecl *TemplateParam, + MultiLevelTemplateArgumentList &Args, + unsigned NewIndex) -> NamedDecl * { + if (auto *TTP = dyn_cast(TemplateParam)) + return transformTemplateTypeParam(SemaRef, DC, TTP, Args, + TTP->getDepth(), NewIndex); + if (auto *TTP = dyn_cast(TemplateParam)) + return transformTemplateParam(SemaRef, DC, TTP, Args, NewIndex, + TTP->getDepth()); + if (auto *NTTP = dyn_cast(TemplateParam)) + return transformTemplateParam(SemaRef, DC, NTTP, Args, NewIndex, + NTTP->getDepth()); + return nullptr; + }; - if (isa(DC)) { - Guide->setAccess(AS_public); - GuideTemplate->setAccess(AS_public); + for (unsigned AliasTemplateParamIdx : DeducedAliasTemplateParams) { + auto *TP = AliasTemplate->getTemplateParameters()->getParam( + AliasTemplateParamIdx); + // Rebuild any internal references to earlier parameters and reindex as + // we go. + MultiLevelTemplateArgumentList Args; + Args.setKind(TemplateSubstitutionKind::Rewrite); + Args.addOuterTemplateArguments(TransformedDeducedAliasArgs); + NamedDecl *NewParam = + TransformTemplateParameter(AliasTemplate->getDeclContext(), TP, Args, + /*NewIndex*/ FPrimeTemplateParams.size()); + FPrimeTemplateParams.push_back(NewParam); + + auto NewTemplateArgument = Context.getCanonicalTemplateArgument( + Context.getInjectedTemplateArg(NewParam)); + TransformedDeducedAliasArgs[AliasTemplateParamIdx] = NewTemplateArgument; + } + // ...followed by the template parameters of f that were not deduced + // (including their default template arguments) + for (unsigned FTemplateParamIdx : NonDeducedTemplateParamsInFIndex) { + auto *TP = F->getTemplateParameters()->getParam(FTemplateParamIdx); + MultiLevelTemplateArgumentList Args; + Args.setKind(TemplateSubstitutionKind::Rewrite); + // We take a shortcut here, it is ok to reuse the + // TemplateArgsForBuildingFPrime. + Args.addOuterTemplateArguments(TemplateArgsForBuildingFPrime); + NamedDecl *NewParam = TransformTemplateParameter( + F->getDeclContext(), TP, Args, FPrimeTemplateParams.size()); + FPrimeTemplateParams.push_back(NewParam); + + assert(TemplateArgsForBuildingFPrime[FTemplateParamIdx].isNull() && + "The argument must be null before setting"); + TemplateArgsForBuildingFPrime[FTemplateParamIdx] = + Context.getCanonicalTemplateArgument( + Context.getInjectedTemplateArg(NewParam)); + } + // FIXME: implement the associated constraint per C++ + // [over.match.class.deduct]p3.3: + // The associated constraints ([temp.constr.decl]) are the + // conjunction of the associated constraints of g and a + // constraint that is satisfied if and only if the arguments + // of A are deducible (see below) from the return type. + auto *FPrimeTemplateParamList = TemplateParameterList::Create( + Context, AliasTemplate->getTemplateParameters()->getTemplateLoc(), + AliasTemplate->getTemplateParameters()->getLAngleLoc(), + FPrimeTemplateParams, + AliasTemplate->getTemplateParameters()->getRAngleLoc(), + /*RequiresClause=*/nullptr); + + // To form a deduction guide f' from f, we leverage clang's instantiation + // mechanism, we construct a template argument list where the template + // arguments refer to the newly-created template parameters of f', and + // then apply instantiation on this template argument list to instantiate + // f, this ensures all template parameter occurrences are updated + // correctly. + // + // The template argument list is formed from the `DeducedArgs`, two parts: + // 1) appeared template parameters of alias: transfrom the deduced + // template argument; + // 2) non-deduced template parameters of f: rebuild a + // template argument; + // + // 2) has been built already (when rebuilding the new template + // parameters), we now perform 1). + MultiLevelTemplateArgumentList Args; + Args.setKind(TemplateSubstitutionKind::Rewrite); + Args.addOuterTemplateArguments(TransformedDeducedAliasArgs); + for (unsigned Index = 0; Index < DeduceResults.size(); ++Index) { + const auto &D = DeduceResults[Index]; + if (D.isNull()) { + // 2): Non-deduced template parameter has been built already. + assert(!TemplateArgsForBuildingFPrime[Index].isNull() && + "template arguments for non-deduced template parameters should " + "be been set!"); + continue; + } + TemplateArgumentLoc Input = SemaRef.getTrivialTemplateArgumentLoc( + D, QualType(), SourceLocation{}); + TemplateArgumentLoc Output; + if (!SemaRef.SubstTemplateArgument(Input, Args, Output)) { + assert(TemplateArgsForBuildingFPrime[Index].isNull() && + "InstantiatedArgs must be null before setting"); + TemplateArgsForBuildingFPrime[Index] = (Output.getArgument()); + } } - DC->addDecl(GuideTemplate); - return GuideTemplate; + auto *TemplateArgListForBuildingFPrime = TemplateArgumentList::CreateCopy( + Context, TemplateArgsForBuildingFPrime); + // Form the f' by substituting the template arguments into f. + if (auto *FPrime = SemaRef.InstantiateFunctionDeclaration( + F, TemplateArgListForBuildingFPrime, AliasTemplate->getLocation(), + Sema::CodeSynthesisContext::BuildingDeductionGuides)) { + auto *GG = dyn_cast(FPrime); + buildDeductionGuide(SemaRef, AliasTemplate, FPrimeTemplateParamList, + GG->getCorrespondingConstructor(), + GG->getExplicitSpecifier(), GG->getTypeSourceInfo(), + AliasTemplate->getBeginLoc(), + AliasTemplate->getLocation(), + AliasTemplate->getEndLoc(), F->isImplicit()); + } } -}; } +} // namespace + FunctionTemplateDecl *Sema::DeclareImplicitDeductionGuideFromInitList( TemplateDecl *Template, MutableArrayRef ParamTypes, SourceLocation Loc) { @@ -2699,6 +3020,10 @@ FunctionTemplateDecl *Sema::DeclareImplicitDeductionGuideFromInitList( void Sema::DeclareImplicitDeductionGuides(TemplateDecl *Template, SourceLocation Loc) { + if (auto *AliasTemplate = llvm::dyn_cast(Template)) { + DeclareImplicitDeductionGuidesForTypeAlias(*this, AliasTemplate, Loc); + return; + } if (CXXRecordDecl *DefRecord = cast(Template->getTemplatedDecl())->getDefinition()) { if (TemplateDecl *DescribedTemplate = DefRecord->getDescribedClassTemplate()) @@ -2714,12 +3039,8 @@ void Sema::DeclareImplicitDeductionGuides(TemplateDecl *Template, if (!isCompleteType(Loc, Transform.DeducedType)) return; - // Check whether we've already declared deduction guides for this template. - // FIXME: Consider storing a flag on the template to indicate this. - auto Existing = DC->lookup(Transform.DeductionGuideName); - for (auto *D : Existing) - if (D->isImplicit()) - return; + if (hasDeclaredDeductionGuides(Transform.DeductionGuideName, DC)) + return; // In case we were expanding a pack when we attempted to declare deduction // guides, turn off pack expansion for everything we're about to do. @@ -5370,6 +5691,15 @@ bool Sema::CheckTemplateTypeArgument( [[fallthrough]]; } default: { + // We allow instantiateing a template with template argument packs when + // building deduction guides. + if (Arg.getKind() == TemplateArgument::Pack && + CodeSynthesisContexts.back().Kind == + Sema::CodeSynthesisContext::BuildingDeductionGuides) { + SugaredConverted.push_back(Arg); + CanonicalConverted.push_back(Arg); + return false; + } // We have a template type parameter but the template argument // is not a type. SourceRange SR = AL.getSourceRange(); diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp index 65f7fa15b20dd..97f8445bf819c 100644 --- a/clang/lib/Sema/SemaTemplateDeduction.cpp +++ b/clang/lib/Sema/SemaTemplateDeduction.cpp @@ -2531,6 +2531,15 @@ DeduceTemplateArguments(Sema &S, TemplateParameterList *TemplateParams, return TemplateDeductionResult::Success; } +TemplateDeductionResult Sema::DeduceTemplateArguments( + TemplateParameterList *TemplateParams, ArrayRef Ps, + ArrayRef As, sema::TemplateDeductionInfo &Info, + SmallVectorImpl &Deduced, + bool NumberOfArgumentsMustMatch) { + return ::DeduceTemplateArguments(*this, TemplateParams, Ps, As, Info, Deduced, + NumberOfArgumentsMustMatch); +} + /// Determine whether two template arguments are the same. static bool isSameTemplateArg(ASTContext &Context, TemplateArgument X, diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp index 371378485626c..d9994d7fd37ad 100644 --- a/clang/lib/Sema/SemaTemplateInstantiate.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -21,6 +21,7 @@ #include "clang/AST/ExprConcepts.h" #include "clang/AST/PrettyDeclStackTrace.h" #include "clang/AST/Type.h" +#include "clang/AST/TypeLoc.h" #include "clang/AST/TypeVisitor.h" #include "clang/Basic/LangOptions.h" #include "clang/Basic/Stack.h" @@ -547,9 +548,9 @@ Sema::InstantiatingTemplate::InstantiatingTemplate( : InstantiatingTemplate(SemaRef, Kind, PointOfInstantiation, InstantiationRange, FunctionTemplate, nullptr, TemplateArgs, &DeductionInfo) { - assert( - Kind == CodeSynthesisContext::ExplicitTemplateArgumentSubstitution || - Kind == CodeSynthesisContext::DeducedTemplateArgumentSubstitution); + assert(Kind == CodeSynthesisContext::ExplicitTemplateArgumentSubstitution || + Kind == CodeSynthesisContext::DeducedTemplateArgumentSubstitution || + Kind == CodeSynthesisContext::BuildingDeductionGuides); } Sema::InstantiatingTemplate::InstantiatingTemplate( @@ -1446,6 +1447,59 @@ namespace { return inherited::TransformFunctionProtoType(TLB, TL); } + QualType TransformInjectedClassNameType(TypeLocBuilder &TLB, + InjectedClassNameTypeLoc TL) { + auto Type = inherited::TransformInjectedClassNameType(TLB, TL); + // Special case for transforming a deduction guide, we return a + // transformed TemplateSpecializationType. + if (Type.isNull() && + SemaRef.CodeSynthesisContexts.back().Kind == + Sema::CodeSynthesisContext::BuildingDeductionGuides) { + // Return a TemplateSpecializationType for transforming a deduction + // guide. + if (auto *ICT = TL.getType()->getAs()) { + auto Type = + inherited::TransformType(ICT->getInjectedSpecializationType()); + TLB.pushTrivial(SemaRef.Context, Type, TL.getNameLoc()); + return Type; + } + } + return Type; + } + // Override the default version to handle a rewrite-template-arg-pack case + // for building a deduction guide. + bool TransformTemplateArgument(const TemplateArgumentLoc &Input, + TemplateArgumentLoc &Output, + bool Uneval = false) { + const TemplateArgument &Arg = Input.getArgument(); + std::vector TArgs; + switch (Arg.getKind()) { + case TemplateArgument::Pack: + // Literally rewrite the template argument pack, instead of unpacking + // it. + assert( + SemaRef.CodeSynthesisContexts.back().Kind == + Sema::CodeSynthesisContext::BuildingDeductionGuides && + "Transforming a template argument pack is only allowed in building " + "deduction guide"); + for (auto &pack : Arg.getPackAsArray()) { + TemplateArgumentLoc Input = SemaRef.getTrivialTemplateArgumentLoc( + pack, QualType(), SourceLocation{}); + TemplateArgumentLoc Output; + if (SemaRef.SubstTemplateArgument(Input, TemplateArgs, Output)) + return true; // fails + TArgs.push_back(Output.getArgument()); + } + Output = SemaRef.getTrivialTemplateArgumentLoc( + TemplateArgument(llvm::ArrayRef(TArgs).copy(SemaRef.Context)), + QualType(), SourceLocation{}); + return false; + default: + break; + } + return inherited::TransformTemplateArgument(Input, Output, Uneval); + } + template QualType TransformFunctionProtoType(TypeLocBuilder &TLB, FunctionProtoTypeLoc TL, @@ -4138,6 +4192,15 @@ Sema::SubstStmt(Stmt *S, const MultiLevelTemplateArgumentList &TemplateArgs) { return Instantiator.TransformStmt(S); } +bool Sema::SubstTemplateArgument( + const TemplateArgumentLoc &Input, + const MultiLevelTemplateArgumentList &TemplateArgs, + TemplateArgumentLoc &Output) { + TemplateInstantiator Instantiator(*this, TemplateArgs, SourceLocation(), + DeclarationName()); + return Instantiator.TransformTemplateArgument(Input, Output); +} + bool Sema::SubstTemplateArguments( ArrayRef Args, const MultiLevelTemplateArgumentList &TemplateArgs, diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp index 9c696e072ba4a..20c2c93ac9c7b 100644 --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -2219,7 +2219,9 @@ Decl *TemplateDeclInstantiator::VisitFunctionDecl( FunctionTemplate->setInstantiatedFromMemberTemplate( D->getDescribedFunctionTemplate()); } - } else if (FunctionTemplate) { + } else if (FunctionTemplate && + SemaRef.CodeSynthesisContexts.back().Kind != + Sema::CodeSynthesisContext::BuildingDeductionGuides) { // Record this function template specialization. ArrayRef Innermost = TemplateArgs.getInnermost(); Function->setFunctionTemplateSpecialization(FunctionTemplate, @@ -4853,16 +4855,13 @@ bool TemplateDeclInstantiator::SubstDefaultedFunction(FunctionDecl *New, /// /// Usually this should not be used, and template argument deduction should be /// used in its place. -FunctionDecl * -Sema::InstantiateFunctionDeclaration(FunctionTemplateDecl *FTD, - const TemplateArgumentList *Args, - SourceLocation Loc) { +FunctionDecl *Sema::InstantiateFunctionDeclaration( + FunctionTemplateDecl *FTD, const TemplateArgumentList *Args, + SourceLocation Loc, CodeSynthesisContext::SynthesisKind CSC) { FunctionDecl *FD = FTD->getTemplatedDecl(); sema::TemplateDeductionInfo Info(Loc); - InstantiatingTemplate Inst( - *this, Loc, FTD, Args->asArray(), - CodeSynthesisContext::ExplicitTemplateArgumentSubstitution, Info); + InstantiatingTemplate Inst(*this, Loc, FTD, Args->asArray(), CSC, Info); if (Inst.isInvalid()) return nullptr; @@ -6286,8 +6285,18 @@ NamedDecl *Sema::FindInstantiatedDecl(SourceLocation Loc, NamedDecl *D, QualType T = CheckTemplateIdType(TemplateName(TD), Loc, Args); if (T.isNull()) return nullptr; - auto *SubstRecord = T->getAsCXXRecordDecl(); - assert(SubstRecord && "class template id not a class type?"); + CXXRecordDecl *SubstRecord = T->getAsCXXRecordDecl(); + + if (!SubstRecord) { + // T can be a dependent TemplateSpecializationType when performing a + // substitution for building a deduction guide. + assert(CodeSynthesisContexts.back().Kind == + CodeSynthesisContext::BuildingDeductionGuides); + // Return a nullptr as a sentinel value, we handle it properly in + // the TemplateInstantiator::TransformInjectedClassNameType + // override, which we transform it to a TemplateSpecializationType. + return nullptr; + } // Check that this template-id names the primary template and not a // partial or explicit specialization. (In the latter cases, it's // meaningless to attempt to find an instantiation of D within the diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 7389a48fe56fc..463ea4a7f0d61 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -4785,6 +4785,14 @@ bool TreeTransform::TransformTemplateArguments( TemplateArgumentLoc In = *First; if (In.getArgument().getKind() == TemplateArgument::Pack) { + // When building the deduction guides, we rewrite the argument packs + // instead of unpacking. + if (getSema().CodeSynthesisContexts.back().Kind == + Sema::CodeSynthesisContext::BuildingDeductionGuides) { + if (getDerived().TransformTemplateArgument(In, Out, Uneval)) + return true; + continue; + } // Unpack argument packs, which we translate them into separate // arguments. // FIXME: We could do much better if we could guarantee that the diff --git a/clang/test/SemaCXX/cxx17-compat.cpp b/clang/test/SemaCXX/cxx17-compat.cpp index d53d80f0d42ca..54ea3384022d4 100644 --- a/clang/test/SemaCXX/cxx17-compat.cpp +++ b/clang/test/SemaCXX/cxx17-compat.cpp @@ -131,3 +131,14 @@ namespace NTTP { // expected-warning@-4 {{non-type template parameter of type 'A' is incompatible with C++ standards before C++20}} #endif } + +namespace CTADForAliasTemplate { +template struct A { A(T); }; +template using B = A; +B b = {1}; +#if __cplusplus <= 201703L + // FIXME: diagnose as well +#else + // expected-warning@-4 {{class template argument deduction for alias templates is incompatible with C++ standards before C++20}} +#endif +} diff --git a/clang/test/SemaCXX/cxx1z-class-template-argument-deduction.cpp b/clang/test/SemaCXX/cxx1z-class-template-argument-deduction.cpp index 33ed4295c2e48..2f067ea53a502 100644 --- a/clang/test/SemaCXX/cxx1z-class-template-argument-deduction.cpp +++ b/clang/test/SemaCXX/cxx1z-class-template-argument-deduction.cpp @@ -101,13 +101,13 @@ namespace dependent { struct B { template struct X { X(T); }; X(int) -> X; - template using Y = X; // expected-note {{template}} + template using Y = X; }; template void f() { typename T::X tx = 0; - typename T::Y ty = 0; // expected-error {{alias template 'Y' requires template arguments; argument deduction only allowed for class templates}} + typename T::Y ty = 0; } - template void f(); // expected-note {{in instantiation of}} + template void f(); template struct C { C(T); }; template C(T) -> C; diff --git a/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp b/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp new file mode 100644 index 0000000000000..794496ed41848 --- /dev/null +++ b/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp @@ -0,0 +1,232 @@ +// RUN: %clang_cc1 -fsyntax-only -Wno-c++11-narrowing -Wno-literal-conversion -std=c++20 -verify %s + +namespace test1 { +template +struct Foo { T t; }; +template +using Bar = Foo; + +Bar s = {1}; +} // namespace test1 + +namespace test2 { +template +struct XYpair { + X x; + Y y; +}; +// A tricky explicit deduction guide that swapping X and Y. +template +XYpair(X, Y) -> XYpair; +template +using AliasXYpair = XYpair; + +AliasXYpair xy = {1.1, 2}; // XYpair +static_assert(__is_same(decltype(xy.x), int)); +static_assert(__is_same(decltype(xy.y), double)); +} // namespace test2 + +namespace test3 { +template +struct container { + // test with default arguments. + container(T a, T b = T()); +}; + +template +using vector = container; +vector v(0, 0); +} // namespace test3 + +namespace test4 { +// Explicit deduction guide. +template +struct X { + T t; + X(T); +}; + +template +X(T) -> X; + +template +using AX = X; + +AX s = {1}; +static_assert(__is_same(decltype(s.t), double)); // explicit one is picked. +} // namespace test4 + +namespace test5 { +template +struct Foo {}; +// Template parameter pack +template +using AF = Foo<1>; +auto a = AF{}; +} // namespace test5 + +namespace test6 { +// non-type template argument. +template +struct Foo { + Foo(T); +}; +template +using AF = Foo; + +AF b{0}; +} // namespace test6 + +namespace test7 { +template +struct Foo { + Foo(T); +}; +// using alias chain. +template +using AF1 = Foo; +template +using AF2 = AF1; +AF2 b = 1; +} // namespace test7 + +namespace test8 { +template +struct Foo { + Foo(T const (&)[N]); +}; + +template +using Bar = Foo; + +Bar s = {{1}}; +} // namespace test8 + +namespace test9 { +template +struct Foo { + Foo(T const (&)[N]); +}; + +template +using Bar = Foo; + +// FIXME: we should reject this case? GCC rejects it, MSVC accepts it. +Bar s = {{1}}; +} // namespace test9 + +namespace test10 { +template +struct Foo { + template + Foo(U); +}; + +template +Foo(U) -> Foo; + +template +using A = Foo; +A a(2); // Foo +} // namespace test10 + +namespace test11 { +struct A {}; +template struct Foo { T c; }; +template using AFoo = Foo; + +AFoo s = {1}; +} // namespace test11 + +namespace test12 { +// no crash on null access attribute +template +struct Foo { + template + struct Bar { + Bar(K); + }; + + template + using ABar = Bar; + void test() { ABar k = 2; } +}; + +void func(Foo s) { + s.test(); +} +} // namespace test12 + +namespace test13 { +template +struct Foo { + Foo(Ts...); +}; + +template +using AFoo = Foo; + +auto b = AFoo{}; +} // namespace test13 + +namespace test14 { +template +concept IsInt = __is_same(decltype(T()), int); + +template +struct Foo { + Foo(T const (&)[N]); +}; + +template +using Bar = Foo; // expected-note {{constraints not satisfied for class template 'Foo'}} +// expected-note@-1 {{candidate template ignored: could not match}} +double abc[3]; +Bar s2 = {abc}; // expected-error {{no viable constructor or deduction guide for deduction }} +} // namespace test14 + +namespace test15 { +template struct Foo { Foo(T); }; + +template using AFoo = Foo; +template concept False = false; +template using BFoo = AFoo; +int i = 0; +AFoo a1(&i); // OK, deduce Foo + +// FIXME: we should reject this case as the W is not deduced from the deduced +// type Foo. +BFoo b2(&i); +} // namespace test15 + +namespace test16 { +struct X { X(int); X(const X&); }; +template +struct Foo { + T t; + Foo(T t) : t(t) {} +}; +template +using AFoo = Foo; +int i = 0; +AFoo s{i}; +static_assert(__is_same(decltype(s.t), int)); + +// explicit deduction guide. +Foo(int) -> Foo; +AFoo s2{i}; +// FIXME: the type should be X because of the above explicit deduction guide. +static_assert(__is_same(decltype(s2.t), int)); +} // namespace test16 + +namespace test17 { +template +struct Foo { T t; }; + +// CTAD for alias templates only works for the RHS of the alias of form of +// [typename] [nested-name-specifier] [template] simple-template-id +template +using AFoo = Foo*; // expected-note {{template is declared here}} + +AFoo s = {1}; // expected-error {{alias template 'AFoo' requires template arguments; argument deduction only allowed for}} +} // namespace test17 diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html index 421b3426b006f..9fbdf347b17c1 100755 --- a/clang/www/cxx_status.html +++ b/clang/www/cxx_status.html @@ -843,7 +843,13 @@

C++20 implementation status

Class template argument deduction for alias templates P1814R0 - No + +
+ Clang 19 (Partial) + The associated constraints (over.match.class.deduct#3.3) for the + synthesized deduction guides are not yet implemented. +
+ Permit conversions to arrays of unknown bound