diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 529dd783ab738..9e67bbb789504 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -281,6 +281,8 @@ Bug Fixes to C++ Support a requires-clause lie at the same depth as those of the surrounding lambda. This, in turn, results in the wrong template argument substitution during constraint checking. (`#78524 `_) +- Clang no longer instantiates the exception specification of discarded candidate function + templates when determining the primary template of an explicit specialization. Bug Fixes to AST Handling ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp index c95c08c0422ed..e91033dd88689 100644 --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/lib/Sema/SemaTemplate.cpp @@ -9709,6 +9709,40 @@ bool Sema::CheckFunctionTemplateSpecialization( // Ignore access information; it doesn't figure into redeclaration checking. FunctionDecl *Specialization = cast(*Result); + // C++23 [except.spec]p13: + // An exception specification is considered to be needed when: + // - [...] + // - the exception specification is compared to that of another declaration + // (e.g., an explicit specialization or an overriding virtual function); + // - [...] + // + // The exception specification of a defaulted function is evaluated as + // described above only when needed; similarly, the noexcept-specifier of a + // specialization of a function template or member function of a class + // template is instantiated only when needed. + // + // The standard doesn't specify what the "comparison with another declaration" + // entails, nor the exact circumstances in which it occurs. Moreover, it does + // not state which properties of an explicit specialization must match the + // primary template. + // + // We assume that an explicit specialization must correspond with (per + // [basic.scope.scope]p4) and declare the same entity as (per [basic.link]p8) + // the declaration produced by substitution into the function template. + // + // Since the determination whether two function declarations correspond does + // not consider exception specification, we only need to instantiate it once + // we determine the primary template when comparing types per + // [basic.link]p11.1. + auto *SpecializationFPT = + Specialization->getType()->castAs(); + // If the function has a dependent exception specification, resolve it after + // we have selected the primary template so we can check whether it matches. + if (getLangOpts().CPlusPlus17 && + isUnresolvedExceptionSpec(SpecializationFPT->getExceptionSpecType()) && + !ResolveExceptionSpec(FD->getLocation(), SpecializationFPT)) + return true; + FunctionTemplateSpecializationInfo *SpecInfo = Specialization->getTemplateSpecializationInfo(); assert(SpecInfo && "Function template specialization info missing?"); diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp index 47cc22310c4ee..563491f76f547 100644 --- a/clang/lib/Sema/SemaTemplateDeduction.cpp +++ b/clang/lib/Sema/SemaTemplateDeduction.cpp @@ -4632,11 +4632,9 @@ TemplateDeductionResult Sema::DeduceTemplateArguments( Info.getLocation())) return TemplateDeductionResult::MiscellaneousDeductionFailure; - // If the function has a dependent exception specification, resolve it now, - // so we can check that the exception specification matches. auto *SpecializationFPT = Specialization->getType()->castAs(); - if (getLangOpts().CPlusPlus17 && + if (IsAddressOfFunction && getLangOpts().CPlusPlus17 && isUnresolvedExceptionSpec(SpecializationFPT->getExceptionSpecType()) && !ResolveExceptionSpec(Info.getLocation(), SpecializationFPT)) return TemplateDeductionResult::MiscellaneousDeductionFailure; @@ -4662,11 +4660,11 @@ TemplateDeductionResult Sema::DeduceTemplateArguments( // specialization with respect to arguments of compatible pointer to function // types, template argument deduction fails. if (!ArgFunctionType.isNull()) { - if (IsAddressOfFunction - ? !isSameOrCompatibleFunctionType( - Context.getCanonicalType(SpecializationType), - Context.getCanonicalType(ArgFunctionType)) - : !Context.hasSameType(SpecializationType, ArgFunctionType)) { + if (IsAddressOfFunction ? !isSameOrCompatibleFunctionType( + Context.getCanonicalType(SpecializationType), + Context.getCanonicalType(ArgFunctionType)) + : !Context.hasSameFunctionTypeIgnoringExceptionSpec( + SpecializationType, ArgFunctionType)) { Info.FirstArg = TemplateArgument(SpecializationType); Info.SecondArg = TemplateArgument(ArgFunctionType); return TemplateDeductionResult::NonDeducedMismatch; diff --git a/clang/test/CXX/except/except.spec/p13.cpp b/clang/test/CXX/except/except.spec/p13.cpp new file mode 100644 index 0000000000000..61cdb74f21ec5 --- /dev/null +++ b/clang/test/CXX/except/except.spec/p13.cpp @@ -0,0 +1,74 @@ +// RUN: %clang_cc1 -fexceptions -fcxx-exceptions -fsyntax-only -verify %s + +struct A { + static constexpr bool x = true; +}; + +namespace N0 { + +template +void f(T, U) noexcept(T::y); // #1 + +template // #2 +void f(T, U*) noexcept(T::x); + +// Deduction should succeed for both candidates, and #2 should be selected as the primary template. +// Only the exception specification of #2 should be instantiated. +template<> +void f(A, int*) noexcept; + +} + +namespace N1 { + +template +void f(T, U) noexcept(T::x); // #1 + +template +void f(T, U*) noexcept(T::y); // #2 +// expected-error@-1 {{no member named 'y' in 'A'}} + +// Deduction should succeed for both candidates, and #2 should be selected as the primary template. +// Only the exception specification of #2 should be instantiated. +template<> +void f(A, int*) noexcept; // expected-error {{exception specification in declaration does not match previous declaration}} + // expected-note@-1 {{in instantiation of exception specification for 'f' requested here}} + // expected-note@-2 {{previous declaration is here}} +} + +namespace N2 { + +template +void f(T, U) noexcept(T::x); + +template +void f(T, U*) noexcept(T::x); + +template +void f(T, U**) noexcept(T::y); // expected-error {{no member named 'y' in 'A'}} + +template +void f(T, U***) noexcept(T::x); + +template<> +void f(A, int*) noexcept; // expected-note {{previous declaration is here}} + +template<> +void f(A, int*); // expected-error {{'f' is missing exception specification 'noexcept'}} + +template<> +void f(A, int**) noexcept; // expected-error {{exception specification in declaration does not match previous declaration}} + // expected-note@-1 {{in instantiation of exception specification for 'f' requested here}} + // expected-note@-2 {{previous declaration is here}} + +// FIXME: Exception specification is currently set to EST_None if instantiation fails. +template<> +void f(A, int**); + +template<> +void f(A, int***) noexcept; // expected-note {{previous declaration is here}} + +template<> +void f(A, int***); // expected-error {{'f' is missing exception specification 'noexcept'}} + +} diff --git a/clang/test/SemaTemplate/class-template-noexcept.cpp b/clang/test/SemaTemplate/class-template-noexcept.cpp index 5c4ac090f3166..14d2e36bc0bfa 100644 --- a/clang/test/SemaTemplate/class-template-noexcept.cpp +++ b/clang/test/SemaTemplate/class-template-noexcept.cpp @@ -2,9 +2,7 @@ // RUN: %clang_cc1 -std=c++11 -verify %s // RUN: %clang_cc1 -std=c++17 -verify %s // RUN: %clang_cc1 -std=c++1z -verify %s -#if __cplusplus >= 201703 -// expected-no-diagnostics -#endif + class A { public: static const char X; @@ -14,19 +12,9 @@ const char A::X = 0; template void func() noexcept(U::X); template -#if __cplusplus >= 201703 -void foo(void(B...) noexcept(x)) {} -#else void foo(void(B...) noexcept(x)) {} // expected-note{{candidate template ignored}} -#endif void bar() { -#if __cplusplus >= 201703 - foo(func); -#else foo(func); // expected-error{{no matching function for call}} -#endif } - -