Skip to content

Commit 6a172a9

Browse files
committed
[clang] Reland: Instantiate concepts with sugared template arguments
Since we don't unique specializations for concepts, we can just instantiate them with the sugared template arguments, at negligible cost. If we don't track their specializations, we can't resugar them later anyway, and that would be more expensive than just instantiating them sugared in the first place since it would require an additional pass. This was a previously reverted patch due to a performance regression, which was very simple to fix, as we were only missing the canonicalizations for the key to the satisfcation cache. Fixes #59271 Differential Revision: https://reviews.llvm.org/D136566
1 parent 8a26c6d commit 6a172a9

File tree

19 files changed

+73
-58
lines changed

19 files changed

+73
-58
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ Improvements to Clang's diagnostics
148148
- Clang now diagnoses undefined behavior in constant expressions more consistently. This includes invalid shifts, and signed overflow in arithmetic.
149149

150150
- -Wdangling-assignment-gsl is enabled by default.
151+
- Clang now does a better job preserving the template arguments as written when specializing concepts.
151152

152153
Improvements to Clang's time-trace
153154
----------------------------------

clang/include/clang/Sema/Template.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,8 @@ enum class TemplateSubstitutionKind : char {
234234
/// Replaces the current 'innermost' level with the provided argument list.
235235
/// This is useful for type deduction cases where we need to get the entire
236236
/// list from the AST, but then add the deduced innermost list.
237-
void replaceInnermostTemplateArguments(Decl *AssociatedDecl, ArgList Args) {
237+
void replaceInnermostTemplateArguments(Decl *AssociatedDecl, ArgList Args,
238+
bool Final = false) {
238239
assert((!TemplateArgumentLists.empty() || NumRetainedOuterLevels) &&
239240
"Replacing in an empty list?");
240241

@@ -246,8 +247,7 @@ enum class TemplateSubstitutionKind : char {
246247
TemplateArgumentLists[0].Args = Args;
247248
} else {
248249
--NumRetainedOuterLevels;
249-
TemplateArgumentLists.push_back(
250-
{{AssociatedDecl, /*Final=*/false}, Args});
250+
TemplateArgumentLists.push_back({{AssociatedDecl, Final}, Args});
251251
}
252252
}
253253

clang/lib/Sema/SemaConcept.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,8 @@ DiagRecursiveConstraintEval(Sema &S, llvm::FoldingSetNodeID &ID,
414414
E->Profile(ID, S.Context, /*Canonical=*/true);
415415
for (const auto &List : MLTAL)
416416
for (const auto &TemplateArg : List.Args)
417-
TemplateArg.Profile(ID, S.Context);
417+
S.Context.getCanonicalTemplateArgument(TemplateArg)
418+
.Profile(ID, S.Context);
418419

419420
// Note that we have to do this with our own collection, because there are
420421
// times where a constraint-expression check can cause us to need to evaluate
@@ -638,8 +639,8 @@ bool Sema::CheckConstraintSatisfaction(
638639
// here.
639640
llvm::SmallVector<TemplateArgument, 4> FlattenedArgs;
640641
for (auto List : TemplateArgsLists)
641-
FlattenedArgs.insert(FlattenedArgs.end(), List.Args.begin(),
642-
List.Args.end());
642+
for (const TemplateArgument &Arg : List.Args)
643+
FlattenedArgs.emplace_back(Context.getCanonicalTemplateArgument(Arg));
643644

644645
llvm::FoldingSetNodeID ID;
645646
ConstraintSatisfaction::Profile(ID, Context, Template, FlattenedArgs);
@@ -823,6 +824,8 @@ Sema::SetupConstraintCheckingTemplateArgumentsAndScope(
823824
/*RelativeToPrimary=*/true,
824825
/*Pattern=*/nullptr,
825826
/*ForConstraintInstantiation=*/true);
827+
if (TemplateArgs)
828+
MLTAL.replaceInnermostTemplateArguments(FD, *TemplateArgs, /*Final=*/true);
826829
if (SetupConstraintScope(FD, TemplateArgs, MLTAL, Scope))
827830
return std::nullopt;
828831

@@ -1476,7 +1479,7 @@ static bool substituteParameterMappings(Sema &S, NormalizedConstraint &N,
14761479
const ConceptSpecializationExpr *CSE) {
14771480
MultiLevelTemplateArgumentList MLTAL = S.getTemplateInstantiationArgs(
14781481
CSE->getNamedConcept(), CSE->getNamedConcept()->getLexicalDeclContext(),
1479-
/*Final=*/false, CSE->getTemplateArguments(),
1482+
/*Final=*/true, CSE->getTemplateArguments(),
14801483
/*RelativeToPrimary=*/true,
14811484
/*Pattern=*/nullptr,
14821485
/*ForConstraintInstantiation=*/true);

clang/lib/Sema/SemaExprCXX.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9330,14 +9330,13 @@ Sema::BuildExprRequirement(
93309330
// be satisfied.
93319331
TemplateParameterList *TPL =
93329332
ReturnTypeRequirement.getTypeConstraintTemplateParameterList();
9333-
QualType MatchedType =
9334-
Context.getReferenceQualifiedType(E).getCanonicalType();
9333+
QualType MatchedType = Context.getReferenceQualifiedType(E);
93359334
llvm::SmallVector<TemplateArgument, 1> Args;
93369335
Args.push_back(TemplateArgument(MatchedType));
93379336

93389337
auto *Param = cast<TemplateTypeParmDecl>(TPL->getParam(0));
93399338

9340-
MultiLevelTemplateArgumentList MLTAL(Param, Args, /*Final=*/false);
9339+
MultiLevelTemplateArgumentList MLTAL(Param, Args, /*Final=*/true);
93419340
MLTAL.addOuterRetainedLevels(TPL->getDepth());
93429341
const TypeConstraint *TC = Param->getTypeConstraint();
93439342
assert(TC && "Type Constraint cannot be null here");

clang/lib/Sema/SemaTemplate.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4358,13 +4358,13 @@ Sema::CheckConceptTemplateId(const CXXScopeSpec &SS,
43584358

43594359
auto *CSD = ImplicitConceptSpecializationDecl::Create(
43604360
Context, NamedConcept->getDeclContext(), NamedConcept->getLocation(),
4361-
CanonicalConverted);
4361+
SugaredConverted);
43624362
ConstraintSatisfaction Satisfaction;
43634363
bool AreArgsDependent =
43644364
TemplateSpecializationType::anyDependentTemplateArguments(
4365-
*TemplateArgs, CanonicalConverted);
4366-
MultiLevelTemplateArgumentList MLTAL(NamedConcept, CanonicalConverted,
4367-
/*Final=*/false);
4365+
*TemplateArgs, SugaredConverted);
4366+
MultiLevelTemplateArgumentList MLTAL(NamedConcept, SugaredConverted,
4367+
/*Final=*/true);
43684368
LocalInstantiationScope Scope(*this);
43694369

43704370
EnterExpressionEvaluationContext EECtx{
@@ -5582,7 +5582,7 @@ bool Sema::CheckTemplateArgumentList(
55825582
CXXThisScopeRAII(*this, RD, ThisQuals, RD != nullptr);
55835583

55845584
MultiLevelTemplateArgumentList MLTAL = getTemplateInstantiationArgs(
5585-
Template, NewContext, /*Final=*/false, CanonicalConverted,
5585+
Template, NewContext, /*Final=*/true, SugaredConverted,
55865586
/*RelativeToPrimary=*/true,
55875587
/*Pattern=*/nullptr,
55885588
/*ForConceptInstantiation=*/true);

clang/lib/Sema/SemaTemplateDeduction.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3078,7 +3078,7 @@ CheckDeducedArgumentConstraints(Sema &S, TemplateDeclT *Template,
30783078
// If we don't need to replace the deduced template arguments,
30793079
// we can add them immediately as the inner-most argument list.
30803080
if (!DeducedArgsNeedReplacement(Template))
3081-
Innermost = CanonicalDeducedArgs;
3081+
Innermost = SugaredDeducedArgs;
30823082

30833083
MultiLevelTemplateArgumentList MLTAL = S.getTemplateInstantiationArgs(
30843084
Template, Template->getDeclContext(), /*Final=*/false, Innermost,
@@ -3090,7 +3090,7 @@ CheckDeducedArgumentConstraints(Sema &S, TemplateDeclT *Template,
30903090
// not class-scope explicit specialization, so replace with Deduced Args
30913091
// instead of adding to inner-most.
30923092
if (!Innermost)
3093-
MLTAL.replaceInnermostTemplateArguments(Template, CanonicalDeducedArgs);
3093+
MLTAL.replaceInnermostTemplateArguments(Template, SugaredDeducedArgs);
30943094

30953095
if (S.CheckConstraintSatisfaction(Template, AssociatedConstraints, MLTAL,
30963096
Info.getLocation(),
@@ -3913,13 +3913,13 @@ TemplateDeductionResult Sema::FinishTemplateArgumentDeduction(
39133913
(CanonicalBuilder.size() ==
39143914
FunctionTemplate->getTemplateParameters()->size())) {
39153915
if (CheckInstantiatedFunctionTemplateConstraints(
3916-
Info.getLocation(), Specialization, CanonicalBuilder,
3916+
Info.getLocation(), Specialization, SugaredBuilder,
39173917
Info.AssociatedConstraintsSatisfaction))
39183918
return TemplateDeductionResult::MiscellaneousDeductionFailure;
39193919

39203920
if (!Info.AssociatedConstraintsSatisfaction.IsSatisfied) {
3921-
Info.reset(Info.takeSugared(),
3922-
TemplateArgumentList::CreateCopy(Context, CanonicalBuilder));
3921+
Info.reset(TemplateArgumentList::CreateCopy(Context, SugaredBuilder),
3922+
Info.takeCanonical());
39233923
return TemplateDeductionResult::ConstraintsNotSatisfied;
39243924
}
39253925
}
@@ -4993,8 +4993,8 @@ static bool CheckDeducedPlaceholderConstraints(Sema &S, const AutoType &Type,
49934993
/*PartialTemplateArgs=*/false,
49944994
SugaredConverted, CanonicalConverted))
49954995
return true;
4996-
MultiLevelTemplateArgumentList MLTAL(Concept, CanonicalConverted,
4997-
/*Final=*/false);
4996+
MultiLevelTemplateArgumentList MLTAL(Concept, SugaredConverted,
4997+
/*Final=*/true);
49984998
// Build up an EvaluationContext with an ImplicitConceptSpecializationDecl so
49994999
// that the template arguments of the constraint can be preserved. For
50005000
// example:
@@ -5008,7 +5008,7 @@ static bool CheckDeducedPlaceholderConstraints(Sema &S, const AutoType &Type,
50085008
S, Sema::ExpressionEvaluationContext::Unevaluated,
50095009
ImplicitConceptSpecializationDecl::Create(
50105010
S.getASTContext(), Concept->getDeclContext(), Concept->getLocation(),
5011-
CanonicalConverted));
5011+
SugaredConverted));
50125012
if (S.CheckConstraintSatisfaction(Concept, {Concept->getConstraintExpr()},
50135013
MLTAL, TypeLoc.getLocalSourceRange(),
50145014
Satisfaction))

clang/lib/Serialization/ASTReaderDecl.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2391,7 +2391,7 @@ void ASTDeclReader::VisitImplicitConceptSpecializationDecl(
23912391
VisitDecl(D);
23922392
llvm::SmallVector<TemplateArgument, 4> Args;
23932393
for (unsigned I = 0; I < D->NumTemplateArgs; ++I)
2394-
Args.push_back(Record.readTemplateArgument(/*Canonicalize=*/true));
2394+
Args.push_back(Record.readTemplateArgument(/*Canonicalize=*/false));
23952395
D->setTemplateArguments(Args);
23962396
}
23972397

clang/test/AST/ast-dump-concepts.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ struct Foo {
2020
// CHECK: TemplateTypeParmDecl {{.*}} referenced Concept {{.*}} 'binary_concept'
2121
// CHECK-NEXT: `-ConceptSpecializationExpr {{.*}} <col:13, col:31> 'bool' Concept {{.*}} 'binary_concept'
2222
// CHECK-NEXT: |-ImplicitConceptSpecializationDecl {{.*}} <line:13:9> col:9
23-
// CHECK-NEXT: | |-TemplateArgument type 'type-parameter-1-0'
24-
// CHECK-NEXT: | | `-TemplateTypeParmType {{.*}} 'type-parameter-1-0' dependent {{.*}}depth 1 index 0
23+
// CHECK-NEXT: | |-TemplateArgument type 'R'
24+
// CHECK-NEXT: | | `-TemplateTypeParmType {{.*}} 'R' dependent {{.*}}depth 1 index 0
25+
// CHECK-NEXT: | | `-TemplateTypeParm {{.*}} 'R'
2526
// CHECK-NEXT: | `-TemplateArgument type 'int'
2627
// CHECK-NEXT: | `-BuiltinType {{.*}} 'int'
2728
// CHECK-NEXT: |-TemplateArgument {{.*}} type 'R'
@@ -35,8 +36,9 @@ struct Foo {
3536
// CHECK: TemplateTypeParmDecl {{.*}} referenced Concept {{.*}} 'unary_concept'
3637
// CHECK-NEXT: `-ConceptSpecializationExpr {{.*}} <col:13> 'bool'
3738
// CHECK-NEXT: |-ImplicitConceptSpecializationDecl {{.*}} <line:10:9> col:9
38-
// CHECK-NEXT: | `-TemplateArgument type 'type-parameter-1-0'
39-
// CHECK-NEXT: | `-TemplateTypeParmType {{.*}} 'type-parameter-1-0' dependent {{.*}}depth 1 index 0
39+
// CHECK-NEXT: | `-TemplateArgument type 'R'
40+
// CHECK-NEXT: | `-TemplateTypeParmType {{.*}} 'R' dependent {{.*}}depth 1 index 0
41+
// CHECK-NEXT: | `-TemplateTypeParm {{.*}} 'R'
4042
template <unary_concept R>
4143
Foo(R);
4244

clang/test/CXX/expr/expr.prim/expr.prim.req/compound-requirement.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,14 @@ using r2i2 = r2<A>; // expected-error{{constraints not satisfied for class templ
3535
using r2i3 = r2<D>;
3636
using r2i4 = r2<const D>; // expected-error{{constraints not satisfied for class template 'r2' [with T = const D]}}
3737

38-
template<typename T> requires requires { { sizeof(T) }; } // expected-note{{because 'sizeof(T)' would be invalid: invalid application of 'sizeof' to an incomplete type 'void'}} expected-note{{because 'sizeof(T)' would be invalid: invalid application of 'sizeof' to an incomplete type 'nonexistent'}}
38+
template<typename T> requires requires { { sizeof(T) }; } // expected-note{{because 'sizeof(T)' would be invalid: invalid application of 'sizeof' to an incomplete type 'void'}} expected-note{{because 'sizeof(T)' would be invalid: invalid application of 'sizeof' to an incomplete type 'class nonexistent'}}
3939
struct r3 {};
4040

4141
using r3i1 = r3<int>;
4242
using r3i2 = r3<A>;
4343
using r3i3 = r3<A &>;
4444
using r3i4 = r3<void>; // expected-error{{constraints not satisfied for class template 'r3' [with T = void]}}
45-
using r3i4 = r3<class nonexistent>; // expected-error{{constraints not satisfied for class template 'r3' [with T = nonexistent]}}
45+
using r3i4 = r3<class nonexistent>; // expected-error{{constraints not satisfied for class template 'r3' [with T = class nonexistent]}}
4646

4747
// Non-dependent expressions
4848

@@ -149,7 +149,7 @@ namespace std_example {
149149
template<typename T> constexpr bool is_same_v<T, T> = true;
150150

151151
template<typename T, typename U> concept same_as = is_same_v<T, U>;
152-
// expected-note@-1 {{because 'is_same_v<int, int *>' evaluated to false}}
152+
// expected-note@-1 {{because 'is_same_v<int, typename T2::inner>' evaluated to false}}
153153

154154
static_assert(C1<int>);
155155
static_assert(C1<int*>);
@@ -173,9 +173,9 @@ namespace std_example {
173173
int operator *() { return 0; }
174174
};
175175
static_assert(C2<T1>);
176-
template<C2 T> struct C2_check {}; // expected-note{{because 'int' does not satisfy 'C2'}} expected-note{{because 'std_example::T2' does not satisfy 'C2'}}
176+
template<C2 T> struct C2_check {}; // expected-note{{because 'int' does not satisfy 'C2'}} expected-note{{because 'T2' does not satisfy 'C2'}}
177177
using c2c1 = C2_check<int>; // expected-error{{constraints not satisfied for class template 'C2_check' [with T = int]}}
178-
using c2c2 = C2_check<T2>; // expected-error{{constraints not satisfied for class template 'C2_check' [with T = std_example::T2]}}
178+
using c2c2 = C2_check<T2>; // expected-error{{constraints not satisfied for class template 'C2_check' [with T = T2]}}
179179

180180
template<typename T>
181181
void g(T t) noexcept(sizeof(T) == 1) {}

clang/test/CXX/expr/expr.prim/expr.prim.req/nested-requirement.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ using r4i = X<void>::r4<int>; // expected-error{{constraints not satisfied for c
2727

2828
// C++ [expr.prim.req.nested] Examples
2929
namespace std_example {
30-
template<typename U> concept C1 = sizeof(U) == 1; // expected-note{{because 'sizeof(int) == 1' (4 == 1) evaluated to false}}
30+
template<typename U> concept C1 = sizeof(U) == 1; // expected-note{{because 'sizeof(decltype(+t)) == 1' (4 == 1) evaluated to false}}
3131
template<typename T> concept D =
3232
requires (T t) {
3333
requires C1<decltype (+t)>; // expected-note{{because 'decltype(+t)' (aka 'int') does not satisfy 'C1'}}

clang/test/CXX/expr/expr.prim/expr.prim.req/simple-requirement.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,14 @@ using r2i4 = r2<const D>; // expected-error{{constraints not satisfied for class
3939

4040
template<typename T> requires requires { sizeof(T); }
4141
// expected-note@-1{{because 'sizeof(T)' would be invalid: invalid application of 'sizeof' to an incomplete type 'void'}}
42-
// expected-note@-2{{because 'sizeof(T)' would be invalid: invalid application of 'sizeof' to an incomplete type 'nonexistent'}}
42+
// expected-note@-2{{because 'sizeof(T)' would be invalid: invalid application of 'sizeof' to an incomplete type 'class nonexistent'}}
4343
struct r3 {};
4444

4545
using r3i1 = r3<int>;
4646
using r3i2 = r3<A>;
4747
using r3i3 = r3<A &>;
4848
using r3i4 = r3<void>; // expected-error{{constraints not satisfied for class template 'r3' [with T = void]}}
49-
using r3i4 = r3<class nonexistent>; // expected-error{{constraints not satisfied for class template 'r3' [with T = nonexistent]}}
49+
using r3i4 = r3<class nonexistent>; // expected-error{{constraints not satisfied for class template 'r3' [with T = class nonexistent]}}
5050

5151
template<typename T> requires requires (T t) { 0; "a"; (void)'a'; }
5252
struct r4 {};

clang/test/CXX/expr/expr.prim/expr.prim.req/type-requirement.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -182,14 +182,14 @@ namespace std_example {
182182
static_assert(C1<has_inner_and_type> && C2<has_inner_and_type> && C3<has_inner_and_type>);
183183
template<C1 T> struct C1_check {};
184184
// expected-note@-1 {{because 'int' does not satisfy 'C1'}}
185-
// expected-note@-2 {{because 'std_example::has_type' does not satisfy 'C1'}}
185+
// expected-note@-2 {{because 'has_type' does not satisfy 'C1'}}
186186
template<C2 T> struct C2_check {};
187-
// expected-note@-1 {{because 'std_example::has_inner' does not satisfy 'C2'}}
187+
// expected-note@-1 {{because 'has_inner' does not satisfy 'C2'}}
188188
template<C3 T> struct C3_check {};
189189
// expected-note@-1 {{because 'void' does not satisfy 'C3'}}
190190
using c1 = C1_check<int>; // expected-error{{constraints not satisfied for class template 'C1_check' [with T = int]}}
191-
using c2 = C1_check<has_type>; // expected-error{{constraints not satisfied for class template 'C1_check' [with T = std_example::has_type]}}
192-
using c3 = C2_check<has_inner>; // expected-error{{constraints not satisfied for class template 'C2_check' [with T = std_example::has_inner]}}
191+
using c2 = C1_check<has_type>; // expected-error{{constraints not satisfied for class template 'C1_check' [with T = has_type]}}
192+
using c3 = C2_check<has_inner>; // expected-error{{constraints not satisfied for class template 'C2_check' [with T = has_inner]}}
193193
using c4 = C3_check<void>; // expected-error{{constraints not satisfied for class template 'C3_check' [with T = void]}}
194194
}
195195

@@ -199,10 +199,10 @@ template <typename T> concept C = requires { requires requires { T::a; }; };
199199
// expected-note@-1 {{because 'T::a' would be invalid: no member named 'a' in 'PR48656::T1'}}
200200

201201
template <C...> struct A {};
202-
// expected-note@-1 {{because 'PR48656::T1' does not satisfy 'C'}}
202+
// expected-note@-1 {{because 'T1' does not satisfy 'C'}}
203203

204204
struct T1 {};
205-
template struct A<T1>; // expected-error {{constraints not satisfied for class template 'A' [with $0 = <PR48656::T1>]}}
205+
template struct A<T1>; // expected-error {{constraints not satisfied for class template 'A' [with $0 = <T1>]}}
206206

207207
struct T2 { static constexpr bool a = false; };
208208
template struct A<T2>;

clang/test/CXX/temp/temp.constr/temp.constr.normal/p1.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ template<typename T> requires Bar<T> && true struct S<T> { };
88

99
template<typename T> concept True2 = sizeof(T) >= 0;
1010
template<typename T> concept Foo2 = True2<T*>;
11-
// expected-error@-1{{'type name' declared as a pointer to a reference of type 'type-parameter-0-0 &'}}
11+
// expected-error@-1{{'type name' declared as a pointer to a reference of type 'T &'}}
1212
template<typename T> concept Bar2 = Foo2<T&>;
1313
// expected-note@-1{{while substituting into concept arguments here; substitution failures not allowed in concept arguments}}
1414
template<typename T> requires Bar2<T> struct S2 { };

clang/test/CXX/temp/temp.param/p10-2a.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ concept OneOf = (is_same_v<T, Ts> || ...);
9494
// expected-note@-5 {{and 'is_same_v<short, char>' evaluated to false}}
9595
// expected-note@-6 3{{because 'is_same_v<int, char[1]>' evaluated to false}}
9696
// expected-note@-7 3{{and 'is_same_v<int, char[2]>' evaluated to false}}
97-
// expected-note@-8 2{{because 'is_same_v<std::nullptr_t, char>' evaluated to false}}
98-
// expected-note@-9 2{{and 'is_same_v<std::nullptr_t, int>' evaluated to false}}
97+
// expected-note@-8 2{{because 'is_same_v<decltype(nullptr), char>' evaluated to false}}
98+
// expected-note@-9 2{{and 'is_same_v<decltype(nullptr), int>' evaluated to false}}
9999

100100
template<OneOf<char[1], char[2]> T, OneOf<int, long, char> U>
101101
// expected-note@-1 2{{because 'OneOf<char, char[1], char[2]>' evaluated to false}}

0 commit comments

Comments
 (0)