Skip to content

Commit 77c5cea

Browse files
authored
[Clang][Sema] Explicit template arguments are not substituted into the exception specification of a function (#90760)
[temp.deduct.general] p6 states: > At certain points in the template argument deduction process it is necessary to take a function type that makes use of template parameters and replace those template parameters with the corresponding template arguments. This is done at the beginning of template argument deduction when any explicitly specified template arguments are substituted into the function type, and again at the end of template argument deduction when any template arguments that were deduced or obtained from default arguments are substituted. [temp.deduct.general] p7 goes on to say: > The _deduction substitution loci_ are > - the function type outside of the _noexcept-specifier_, > - the explicit-specifier, > - the template parameter declarations, and > - the template argument list of a partial specialization > > The substitution occurs in all types and expressions that are used in the deduction substitution loci. [...] Consider the following: ```cpp struct A { static constexpr bool x = true; }; template<typename T, typename U> void f(T, U) noexcept(T::x); // #1 template<typename T, typename U> void f(T, U*) noexcept(T::y); // #2 template<> void f<A>(A, int*) noexcept; // clang currently accepts, GCC and EDG reject ``` Currently, `Sema::SubstituteExplicitTemplateArguments` will substitute into the _noexcept-specifier_ when deducing template arguments from a function declaration or when deducing template arguments for taking the address of a function template (and the substitution is treated as a SFINAE context). In the above example, `#1` is selected as the primary template because substitution of the explicit template arguments into the _noexcept-specifier_ of `#2` failed, which resulted in the candidate being ignored. This behavior is incorrect ([temp.deduct.general] note 4 says as much), and this patch corrects it by deferring all substitution into the _noexcept-specifier_ until it is instantiated. As part of the necessary changes to make this patch work, the instantiation of the exception specification of a function template specialization when taking the address of a function template is changed to only occur for the function selected by overload resolution per [except.spec] p13.1 (as opposed to being instantiated for every candidate).
1 parent 04d0a69 commit 77c5cea

File tree

8 files changed

+75
-51
lines changed

8 files changed

+75
-51
lines changed

clang/docs/ReleaseNotes.rst

+2
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,8 @@ Bug Fixes to C++ Support
691691
- Fix an assertion failure when parsing an invalid members of an anonymous class. (#GH85447)
692692
- Fixed a misuse of ``UnresolvedLookupExpr`` for ill-formed templated expressions. Fixes (#GH48673), (#GH63243)
693693
and (#GH88832).
694+
- Clang now defers all substitution into the exception specification of a function template specialization
695+
until the noexcept-specifier is instantiated.
694696

695697
Bug Fixes to AST Handling
696698
^^^^^^^^^^^^^^^^^^^^^^^^^

clang/lib/Sema/SemaInit.cpp

+18-6
Original file line numberDiff line numberDiff line change
@@ -6576,12 +6576,12 @@ void InitializationSequence::InitializeFrom(Sema &S,
65766576

65776577
AddPassByIndirectCopyRestoreStep(DestType, ShouldCopy);
65786578
} else if (ICS.isBad()) {
6579-
DeclAccessPair dap;
6580-
if (isLibstdcxxPointerReturnFalseHack(S, Entity, Initializer)) {
6579+
if (isLibstdcxxPointerReturnFalseHack(S, Entity, Initializer))
65816580
AddZeroInitializationStep(Entity.getType());
6582-
} else if (Initializer->getType() == Context.OverloadTy &&
6583-
!S.ResolveAddressOfOverloadedFunction(Initializer, DestType,
6584-
false, dap))
6581+
else if (DeclAccessPair Found;
6582+
Initializer->getType() == Context.OverloadTy &&
6583+
!S.ResolveAddressOfOverloadedFunction(Initializer, DestType,
6584+
/*Complain=*/false, Found))
65856585
SetFailed(InitializationSequence::FK_AddressOfOverloadFailed);
65866586
else if (Initializer->getType()->isFunctionType() &&
65876587
isExprAnUnaddressableFunction(S, Initializer))
@@ -9641,6 +9641,8 @@ bool InitializationSequence::Diagnose(Sema &S,
96419641
if (!Failed())
96429642
return false;
96439643

9644+
QualType DestType = Entity.getType();
9645+
96449646
// When we want to diagnose only one element of a braced-init-list,
96459647
// we need to factor it out.
96469648
Expr *OnlyArg;
@@ -9650,11 +9652,21 @@ bool InitializationSequence::Diagnose(Sema &S,
96509652
OnlyArg = List->getInit(0);
96519653
else
96529654
OnlyArg = Args[0];
9655+
9656+
if (OnlyArg->getType() == S.Context.OverloadTy) {
9657+
DeclAccessPair Found;
9658+
if (FunctionDecl *FD = S.ResolveAddressOfOverloadedFunction(
9659+
OnlyArg, DestType.getNonReferenceType(), /*Complain=*/false,
9660+
Found)) {
9661+
if (Expr *Resolved =
9662+
S.FixOverloadedFunctionReference(OnlyArg, Found, FD).get())
9663+
OnlyArg = Resolved;
9664+
}
9665+
}
96539666
}
96549667
else
96559668
OnlyArg = nullptr;
96569669

9657-
QualType DestType = Entity.getType();
96589670
switch (Failure) {
96599671
case FK_TooManyInitsForReference:
96609672
// FIXME: Customize for the initialized entity?

clang/lib/Sema/SemaTemplateDeduction.cpp

+3-29
Original file line numberDiff line numberDiff line change
@@ -1323,13 +1323,11 @@ bool Sema::isSameOrCompatibleFunctionType(QualType P, QualType A) {
13231323
return Context.hasSameType(P, A);
13241324

13251325
// Noreturn and noexcept adjustment.
1326-
QualType AdjustedParam;
1327-
if (IsFunctionConversion(P, A, AdjustedParam))
1328-
return Context.hasSameType(AdjustedParam, A);
1326+
if (QualType AdjustedParam; IsFunctionConversion(P, A, AdjustedParam))
1327+
P = AdjustedParam;
13291328

13301329
// FIXME: Compatible calling conventions.
1331-
1332-
return Context.hasSameType(P, A);
1330+
return Context.hasSameFunctionTypeIgnoringExceptionSpec(P, A);
13331331
}
13341332

13351333
/// Get the index of the first template parameter that was originally from the
@@ -3509,23 +3507,6 @@ TemplateDeductionResult Sema::SubstituteExplicitTemplateArguments(
35093507
if (FunctionType) {
35103508
auto EPI = Proto->getExtProtoInfo();
35113509
EPI.ExtParameterInfos = ExtParamInfos.getPointerOrNull(ParamTypes.size());
3512-
3513-
// In C++1z onwards, exception specifications are part of the function type,
3514-
// so substitution into the type must also substitute into the exception
3515-
// specification.
3516-
SmallVector<QualType, 4> ExceptionStorage;
3517-
if (getLangOpts().CPlusPlus17 &&
3518-
SubstExceptionSpec(
3519-
Function->getLocation(), EPI.ExceptionSpec, ExceptionStorage,
3520-
getTemplateInstantiationArgs(
3521-
FunctionTemplate, nullptr, /*Final=*/true,
3522-
/*Innermost=*/SugaredExplicitArgumentList->asArray(),
3523-
/*RelativeToPrimary=*/false,
3524-
/*Pattern=*/nullptr,
3525-
/*ForConstraintInstantiation=*/false,
3526-
/*SkipForSpecialization=*/true)))
3527-
return TemplateDeductionResult::SubstitutionFailure;
3528-
35293510
*FunctionType = BuildFunctionType(ResultType, ParamTypes,
35303511
Function->getLocation(),
35313512
Function->getDeclName(),
@@ -4705,13 +4686,6 @@ TemplateDeductionResult Sema::DeduceTemplateArguments(
47054686
Info.getLocation()))
47064687
return TemplateDeductionResult::MiscellaneousDeductionFailure;
47074688

4708-
auto *SpecializationFPT =
4709-
Specialization->getType()->castAs<FunctionProtoType>();
4710-
if (IsAddressOfFunction && getLangOpts().CPlusPlus17 &&
4711-
isUnresolvedExceptionSpec(SpecializationFPT->getExceptionSpecType()) &&
4712-
!ResolveExceptionSpec(Info.getLocation(), SpecializationFPT))
4713-
return TemplateDeductionResult::MiscellaneousDeductionFailure;
4714-
47154689
// Adjust the exception specification of the argument to match the
47164690
// substituted and resolved type we just formed. (Calling convention and
47174691
// noreturn can't be dependent, so we don't actually need this for them

clang/test/CXX/drs/dr13xx.cpp

+5-8
Original file line numberDiff line numberDiff line change
@@ -281,13 +281,10 @@ namespace cwg1330 { // cwg1330: 4 c++11
281281
decltype(f<char>()) f2; // #cwg1330-f-char
282282
bool f3 = noexcept(f<float>()); /// #cwg1330-f-float
283283
#endif
284-
// In C++17 onwards, substituting explicit template arguments into the
285-
// function type substitutes into the exception specification (because it's
286-
// part of the type). In earlier languages, we don't notice there's a problem
287-
// until we've already started to instantiate.
288284
template int f<short>(); // #cwg1330-f-short
289-
// since-cxx17-error@-1 {{explicit instantiation of 'f' does not refer to a function template, variable template, member function, member class, or static data member}}
290-
// since-cxx17-note@#cwg1330-f {{candidate template ignored: substitution failure [with T = short]: type 'short' cannot be used prior to '::' because it has no members}}
285+
// since-cxx17-error@#cwg1330-f {{type 'short' cannot be used prior to '::' because it has no members}}
286+
// since-cxx17-note@#cwg1330-f {{in instantiation of exception specification for 'f<short>' requested here}}
287+
// since-cxx17-note@#cwg1330-f-short {{in instantiation of function template specialization 'cwg1330::f<short>' requested here}}
291288

292289
template<typename T> struct C {
293290
C() throw(typename T::type); // #cwg1330-C
@@ -500,7 +497,7 @@ namespace cwg1359 { // cwg1359: 3.5
500497
union B { constexpr B() = default; int a; }; // #cwg1359-B
501498
// cxx11-17-error@-1 {{defaulted definition of default constructor cannot be marked constexpr before C++23}}
502499
union C { constexpr C() = default; int a, b; }; // #cwg1359-C
503-
// cxx11-17-error@-1 {{defaulted definition of default constructor cannot be marked constexpr}}
500+
// cxx11-17-error@-1 {{defaulted definition of default constructor cannot be marked constexpr}}
504501
struct X { constexpr X() = default; union {}; };
505502
// since-cxx11-error@-1 {{declaration does not declare anything}}
506503
struct Y { constexpr Y() = default; union { int a; }; }; // #cwg1359-Y
@@ -720,7 +717,7 @@ struct A {
720717
} // namespace cwg1397
721718

722719
namespace cwg1399 { // cwg1399: dup 1388
723-
template<typename ...T> void f(T..., int, T...) {} // #cwg1399-f
720+
template<typename ...T> void f(T..., int, T...) {} // #cwg1399-f
724721
// cxx98-error@-1 {{variadic templates are a C++11 extension}}
725722
void g() {
726723
f(0);

clang/test/CXX/except/except.spec/p13.cpp

+27
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,30 @@ template<>
7272
void f(A, int***); // expected-error {{'f<A, int>' is missing exception specification 'noexcept'}}
7373

7474
}
75+
76+
namespace N3 {
77+
78+
template<typename T, typename U>
79+
void f(T, U) noexcept(T::y); // #1
80+
81+
template<typename T, typename U> // #2
82+
void f(T, U*) noexcept(T::x);
83+
84+
// Deduction should succeed for both candidates, and #2 should be selected by overload resolution.
85+
// Only the exception specification of #2 should be instantiated.
86+
void (*x)(A, int*) = f;
87+
}
88+
89+
namespace N4 {
90+
91+
template<typename T, typename U>
92+
void f(T, U) noexcept(T::x); // #1
93+
94+
template<typename T, typename U>
95+
void f(T, U*) noexcept(T::y); // #2
96+
// expected-error@-1 {{no member named 'y' in 'A'}}
97+
98+
// Deduction should succeed for both candidates, and #2 should be selected by overload resolution.
99+
// Only the exception specification of #2 should be instantiated.
100+
void (*x)(A, int*) = f; // expected-note {{in instantiation of exception specification for 'f<A, int>' requested here}}
101+
}
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// RUN: %clang_cc1 -verify %s
2+
3+
struct A {
4+
static constexpr bool x = true;
5+
};
6+
7+
template<typename T, typename U>
8+
void f(T, U) noexcept(T::x);
9+
10+
template<typename T, typename U>
11+
void f(T, U*) noexcept(T::y); // expected-error {{no member named 'y' in 'A'}}
12+
13+
template<>
14+
void f<A>(A, int*); // expected-note {{in instantiation of exception specification}}

clang/test/SemaCXX/cxx1z-noexcept-function-type.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ template<typename A, typename B> void redecl3() throw(B); // expected-error {{do
1818

1919
typedef int I;
2020
template<bool B> void redecl4(I) noexcept(B);
21-
template<bool B> void redecl4(I) noexcept(B); // expected-note {{could not match 'void (I) noexcept(false)' (aka 'void (int) noexcept(false)') against 'void (int) noexcept'}}
21+
template<bool B> void redecl4(I) noexcept(B);
2222

2323
void (*init_with_exact_type_a)(int) noexcept = redecl4<true>;
2424
void (*init_with_mismatched_type_a)(int) = redecl4<true>;
@@ -27,7 +27,7 @@ using DeducedType_a = decltype(deduce_auto_from_noexcept_function_ptr_a);
2727
using DeducedType_a = void (*)(int) noexcept;
2828

2929
void (*init_with_exact_type_b)(int) = redecl4<false>;
30-
void (*init_with_mismatched_type_b)(int) noexcept = redecl4<false>; // expected-error {{does not match required type}}
30+
void (*init_with_mismatched_type_b)(int) noexcept = redecl4<false>; // expected-error {{cannot initialize a variable of type}}
3131
auto deduce_auto_from_noexcept_function_ptr_b = redecl4<false>;
3232
using DeducedType_b = decltype(deduce_auto_from_noexcept_function_ptr_b);
3333
using DeducedType_b = void (*)(int);

clang/test/SemaTemplate/temp_arg_type.cpp

+4-6
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ A<0> *a1; // expected-error{{template argument for template type parameter must
1111
A<A> *a2; // expected-error{{use of class template 'A' requires template arguments}}
1212

1313
A<int> *a3;
14-
A<int()> *a4;
14+
A<int()> *a4;
1515
A<int(float)> *a5;
1616
A<A<int> > *a6;
1717

@@ -95,15 +95,13 @@ namespace deduce_noexcept {
9595
template void dep() noexcept(true); // expected-error {{does not refer to a function template}}
9696
template void dep() noexcept(false); // expected-error {{does not refer to a function template}}
9797

98-
// FIXME: It's also not clear whether this should be valid: do we substitute
99-
// into the function type (including the exception specification) or not?
100-
template<typename T> typename T::type1 f() noexcept(T::a);
101-
template<typename T> typename T::type2 f() noexcept(T::b) {}
98+
template<typename T> typename T::type1 f() noexcept(T::a); // expected-note {{candidate}}
99+
template<typename T> typename T::type2 f() noexcept(T::b) {} // expected-note {{candidate}}
102100
struct X {
103101
static constexpr bool b = true;
104102
using type1 = void;
105103
using type2 = void;
106104
};
107-
template void f<X>();
105+
template void f<X>(); // expected-error {{partial ordering for explicit instantiation of 'f' is ambiguous}}
108106
}
109107
#endif

0 commit comments

Comments
 (0)