Skip to content

Commit f81f09b

Browse files
committed
[c++20] For P0732R2: Support string literal operator templates.
1 parent a222d83 commit f81f09b

File tree

5 files changed

+164
-42
lines changed

5 files changed

+164
-42
lines changed

clang/include/clang/Sema/Sema.h

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3889,7 +3889,7 @@ class Sema final {
38893889
/// The lookup found an overload set of literal operator templates,
38903890
/// which expect the character type and characters of the spelling of the
38913891
/// string literal token to be passed as template arguments.
3892-
LOLR_StringTemplate
3892+
LOLR_StringTemplatePack,
38933893
};
38943894

38953895
SpecialMemberOverloadResult LookupSpecialMember(CXXRecordDecl *D,
@@ -3997,12 +3997,11 @@ class Sema final {
39973997
CXXDestructorDecl *LookupDestructor(CXXRecordDecl *Class);
39983998

39993999
bool checkLiteralOperatorId(const CXXScopeSpec &SS, const UnqualifiedId &Id);
4000-
LiteralOperatorLookupResult LookupLiteralOperator(Scope *S, LookupResult &R,
4001-
ArrayRef<QualType> ArgTys,
4002-
bool AllowRaw,
4003-
bool AllowTemplate,
4004-
bool AllowStringTemplate,
4005-
bool DiagnoseMissing);
4000+
LiteralOperatorLookupResult
4001+
LookupLiteralOperator(Scope *S, LookupResult &R, ArrayRef<QualType> ArgTys,
4002+
bool AllowRaw, bool AllowTemplate,
4003+
bool AllowStringTemplate, bool DiagnoseMissing,
4004+
StringLiteral *StringLit = nullptr);
40064005
bool isKnownName(StringRef name);
40074006

40084007
/// Status of the function emission on the CUDA/HIP/OpenMP host/device attrs.

clang/lib/Sema/SemaDeclCXX.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15522,6 +15522,18 @@ checkLiteralOperatorTemplateParameterList(Sema &SemaRef,
1552215522
SemaRef.Context.hasSameType(PmDecl->getType(), SemaRef.Context.CharTy))
1552315523
return false;
1552415524

15525+
// C++20 [over.literal]p5:
15526+
// A string literal operator template is a literal operator template
15527+
// whose template-parameter-list comprises a single non-type
15528+
// template-parameter of class type.
15529+
//
15530+
// As a DR resolution, we also allow placeholders for deduced class
15531+
// template specializations.
15532+
if (SemaRef.getLangOpts().CPlusPlus20 &&
15533+
!PmDecl->isTemplateParameterPack() &&
15534+
(PmDecl->getType()->isRecordType() ||
15535+
PmDecl->getType()->getAs<DeducedTemplateSpecializationType>()))
15536+
return false;
1552515537
} else if (TemplateParams->size() == 2) {
1552615538
TemplateTypeParmDecl *PmType =
1552715539
dyn_cast<TemplateTypeParmDecl>(TemplateParams->getParam(0));
@@ -15578,6 +15590,8 @@ bool Sema::CheckLiteralOperatorDeclaration(FunctionDecl *FnDecl) {
1557815590
// template <char...> type operator "" name() and
1557915591
// template <class T, T...> type operator "" name() are the only valid
1558015592
// template signatures, and the only valid signatures with no parameters.
15593+
//
15594+
// C++20 also allows template <SomeClass T> type operator "" name().
1558115595
if (TpDecl) {
1558215596
if (FnDecl->param_size() != 0) {
1558315597
Diag(FnDecl->getLocation(),

clang/lib/Sema/SemaExpr.cpp

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1758,7 +1758,7 @@ static ExprResult BuildCookedLiteralOperatorCall(Sema &S, Scope *Scope,
17581758
LookupResult R(S, OpName, UDSuffixLoc, Sema::LookupOrdinaryName);
17591759
if (S.LookupLiteralOperator(Scope, R, llvm::makeArrayRef(ArgTy, Args.size()),
17601760
/*AllowRaw*/ false, /*AllowTemplate*/ false,
1761-
/*AllowStringTemplate*/ false,
1761+
/*AllowStringTemplatePack*/ false,
17621762
/*DiagnoseMissing*/ true) == Sema::LOLR_Error)
17631763
return ExprError();
17641764

@@ -1863,9 +1863,9 @@ Sema::ActOnStringLiteral(ArrayRef<Token> StringToks, Scope *UDLScope) {
18631863

18641864
LookupResult R(*this, OpName, UDSuffixLoc, LookupOrdinaryName);
18651865
switch (LookupLiteralOperator(UDLScope, R, ArgTy,
1866-
/*AllowRaw*/ false, /*AllowTemplate*/ false,
1867-
/*AllowStringTemplate*/ true,
1868-
/*DiagnoseMissing*/ true)) {
1866+
/*AllowRaw*/ false, /*AllowTemplate*/ true,
1867+
/*AllowStringTemplatePack*/ true,
1868+
/*DiagnoseMissing*/ true, Lit)) {
18691869

18701870
case LOLR_Cooked: {
18711871
llvm::APInt Len(Context.getIntWidth(SizeType), Literal.GetNumStringChars());
@@ -1876,7 +1876,16 @@ Sema::ActOnStringLiteral(ArrayRef<Token> StringToks, Scope *UDLScope) {
18761876
return BuildLiteralOperatorCall(R, OpNameInfo, Args, StringTokLocs.back());
18771877
}
18781878

1879-
case LOLR_StringTemplate: {
1879+
case LOLR_Template: {
1880+
TemplateArgumentListInfo ExplicitArgs;
1881+
TemplateArgument Arg(Lit);
1882+
TemplateArgumentLocInfo ArgInfo(Lit);
1883+
ExplicitArgs.addArgument(TemplateArgumentLoc(Arg, ArgInfo));
1884+
return BuildLiteralOperatorCall(R, OpNameInfo, None, StringTokLocs.back(),
1885+
&ExplicitArgs);
1886+
}
1887+
1888+
case LOLR_StringTemplatePack: {
18801889
TemplateArgumentListInfo ExplicitArgs;
18811890

18821891
unsigned CharBits = Context.getIntWidth(CharTy);
@@ -1897,7 +1906,6 @@ Sema::ActOnStringLiteral(ArrayRef<Token> StringToks, Scope *UDLScope) {
18971906
&ExplicitArgs);
18981907
}
18991908
case LOLR_Raw:
1900-
case LOLR_Template:
19011909
case LOLR_ErrorNoDiagnostic:
19021910
llvm_unreachable("unexpected literal operator lookup result");
19031911
case LOLR_Error:
@@ -3641,7 +3649,7 @@ ExprResult Sema::ActOnNumericConstant(const Token &Tok, Scope *UDLScope) {
36413649
LookupResult R(*this, OpName, UDSuffixLoc, LookupOrdinaryName);
36423650
switch (LookupLiteralOperator(UDLScope, R, CookedTy,
36433651
/*AllowRaw*/ true, /*AllowTemplate*/ true,
3644-
/*AllowStringTemplate*/ false,
3652+
/*AllowStringTemplatePack*/ false,
36453653
/*DiagnoseMissing*/ !Literal.isImaginary)) {
36463654
case LOLR_ErrorNoDiagnostic:
36473655
// Lookup failure for imaginary constants isn't fatal, there's still the
@@ -3696,7 +3704,7 @@ ExprResult Sema::ActOnNumericConstant(const Token &Tok, Scope *UDLScope) {
36963704
return BuildLiteralOperatorCall(R, OpNameInfo, None, TokLoc,
36973705
&ExplicitArgs);
36983706
}
3699-
case LOLR_StringTemplate:
3707+
case LOLR_StringTemplatePack:
37003708
llvm_unreachable("unexpected literal operator lookup result");
37013709
}
37023710
}

clang/lib/Sema/SemaLookup.cpp

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3335,20 +3335,21 @@ CXXDestructorDecl *Sema::LookupDestructor(CXXRecordDecl *Class) {
33353335
/// and filter the results to the appropriate set for the given argument types.
33363336
Sema::LiteralOperatorLookupResult
33373337
Sema::LookupLiteralOperator(Scope *S, LookupResult &R,
3338-
ArrayRef<QualType> ArgTys,
3339-
bool AllowRaw, bool AllowTemplate,
3340-
bool AllowStringTemplate, bool DiagnoseMissing) {
3338+
ArrayRef<QualType> ArgTys, bool AllowRaw,
3339+
bool AllowTemplate, bool AllowStringTemplatePack,
3340+
bool DiagnoseMissing, StringLiteral *StringLit) {
33413341
LookupName(R, S);
33423342
assert(R.getResultKind() != LookupResult::Ambiguous &&
33433343
"literal operator lookup can't be ambiguous");
33443344

33453345
// Filter the lookup results appropriately.
33463346
LookupResult::Filter F = R.makeFilter();
33473347

3348+
bool AllowCooked = true;
33483349
bool FoundRaw = false;
33493350
bool FoundTemplate = false;
3350-
bool FoundStringTemplate = false;
3351-
bool FoundExactMatch = false;
3351+
bool FoundStringTemplatePack = false;
3352+
bool FoundCooked = false;
33523353

33533354
while (F.hasNext()) {
33543355
Decl *D = F.next();
@@ -3363,60 +3364,88 @@ Sema::LookupLiteralOperator(Scope *S, LookupResult &R,
33633364

33643365
bool IsRaw = false;
33653366
bool IsTemplate = false;
3366-
bool IsStringTemplate = false;
3367-
bool IsExactMatch = false;
3367+
bool IsStringTemplatePack = false;
3368+
bool IsCooked = false;
33683369

33693370
if (FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
33703371
if (FD->getNumParams() == 1 &&
33713372
FD->getParamDecl(0)->getType()->getAs<PointerType>())
33723373
IsRaw = true;
33733374
else if (FD->getNumParams() == ArgTys.size()) {
3374-
IsExactMatch = true;
3375+
IsCooked = true;
33753376
for (unsigned ArgIdx = 0; ArgIdx != ArgTys.size(); ++ArgIdx) {
33763377
QualType ParamTy = FD->getParamDecl(ArgIdx)->getType();
33773378
if (!Context.hasSameUnqualifiedType(ArgTys[ArgIdx], ParamTy)) {
3378-
IsExactMatch = false;
3379+
IsCooked = false;
33793380
break;
33803381
}
33813382
}
33823383
}
33833384
}
33843385
if (FunctionTemplateDecl *FD = dyn_cast<FunctionTemplateDecl>(D)) {
33853386
TemplateParameterList *Params = FD->getTemplateParameters();
3386-
if (Params->size() == 1)
3387+
if (Params->size() == 1) {
33873388
IsTemplate = true;
3388-
else
3389-
IsStringTemplate = true;
3389+
3390+
// A string literal template is only considered if the string literal
3391+
// is a well-formed template argument for the template parameter.
3392+
if (StringLit) {
3393+
SFINAETrap Trap(*this);
3394+
SmallVector<TemplateArgument, 1> Checked;
3395+
TemplateArgumentLoc Arg(TemplateArgument(StringLit), StringLit);
3396+
if (CheckTemplateArgument(Params->getParam(0), Arg, FD,
3397+
R.getNameLoc(), R.getNameLoc(), 0,
3398+
Checked) ||
3399+
Trap.hasErrorOccurred())
3400+
IsTemplate = false;
3401+
}
3402+
} else {
3403+
IsStringTemplatePack = true;
3404+
}
33903405
}
33913406

3392-
if (IsExactMatch) {
3393-
FoundExactMatch = true;
3407+
if (AllowTemplate && StringLit && IsTemplate) {
3408+
FoundTemplate = true;
33943409
AllowRaw = false;
3395-
AllowTemplate = false;
3396-
AllowStringTemplate = false;
3397-
if (FoundRaw || FoundTemplate || FoundStringTemplate) {
3410+
AllowCooked = false;
3411+
AllowStringTemplatePack = false;
3412+
if (FoundRaw || FoundCooked || FoundStringTemplatePack) {
3413+
F.restart();
3414+
FoundRaw = FoundCooked = FoundStringTemplatePack = false;
3415+
}
3416+
} else if (AllowCooked && IsCooked) {
3417+
FoundCooked = true;
3418+
AllowRaw = false;
3419+
AllowTemplate = StringLit;
3420+
AllowStringTemplatePack = false;
3421+
if (FoundRaw || FoundTemplate || FoundStringTemplatePack) {
33983422
// Go through again and remove the raw and template decls we've
33993423
// already found.
34003424
F.restart();
3401-
FoundRaw = FoundTemplate = FoundStringTemplate = false;
3425+
FoundRaw = FoundTemplate = FoundStringTemplatePack = false;
34023426
}
34033427
} else if (AllowRaw && IsRaw) {
34043428
FoundRaw = true;
34053429
} else if (AllowTemplate && IsTemplate) {
34063430
FoundTemplate = true;
3407-
} else if (AllowStringTemplate && IsStringTemplate) {
3408-
FoundStringTemplate = true;
3431+
} else if (AllowStringTemplatePack && IsStringTemplatePack) {
3432+
FoundStringTemplatePack = true;
34093433
} else {
34103434
F.erase();
34113435
}
34123436
}
34133437

34143438
F.done();
34153439

3440+
// Per C++20 [lex.ext]p5, we prefer the template form over the non-template
3441+
// form for string literal operator templates.
3442+
if (StringLit && FoundTemplate)
3443+
return LOLR_Template;
3444+
34163445
// C++11 [lex.ext]p3, p4: If S contains a literal operator with a matching
34173446
// parameter type, that is used in preference to a raw literal operator
34183447
// or literal operator template.
3419-
if (FoundExactMatch)
3448+
if (FoundCooked)
34203449
return LOLR_Cooked;
34213450

34223451
// C++11 [lex.ext]p3, p4: S shall contain a raw literal operator or a literal
@@ -3434,15 +3463,15 @@ Sema::LookupLiteralOperator(Scope *S, LookupResult &R,
34343463
if (FoundTemplate)
34353464
return LOLR_Template;
34363465

3437-
if (FoundStringTemplate)
3438-
return LOLR_StringTemplate;
3466+
if (FoundStringTemplatePack)
3467+
return LOLR_StringTemplatePack;
34393468

34403469
// Didn't find anything we could use.
34413470
if (DiagnoseMissing) {
34423471
Diag(R.getNameLoc(), diag::err_ovl_no_viable_literal_operator)
34433472
<< R.getLookupName() << (int)ArgTys.size() << ArgTys[0]
34443473
<< (ArgTys.size() == 2 ? ArgTys[1] : QualType()) << AllowRaw
3445-
<< (AllowTemplate || AllowStringTemplate);
3474+
<< (AllowTemplate || AllowStringTemplatePack);
34463475
return LOLR_Error;
34473476
}
34483477

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,92 @@
1-
// RUN: %clang_cc1 -fsyntax-only -std=c++11 -verify %s -triple=x86_64-linux-gnu
1+
// RUN: %clang_cc1 -fsyntax-only -std=c++11 -verify %s -fcxx-exceptions -triple=x86_64-linux-gnu
2+
// RUN: %clang_cc1 -fsyntax-only -std=c++20 -verify %s -fcxx-exceptions -triple=x86_64-linux-gnu
23

34
using size_t = decltype(sizeof(int));
45

56
int &operator "" _x1 (const char *);
67
double &operator "" _x1 (const char *, size_t);
78
double &i1 = "foo"_x1;
8-
double &i2 = u8"foo"_x1;
9+
#if __cplusplus >= 202002L
10+
using char8 = float;
11+
float &operator "" _x1 (const char8_t *, size_t);
12+
#else
13+
using char8 = double;
14+
#endif
15+
char8 &i2 = u8"foo"_x1;
916
double &i3 = L"foo"_x1; // expected-error {{no matching literal operator for call to 'operator""_x1' with arguments of types 'const wchar_t *' and 'unsigned long'}}
1017

1118
char &operator "" _x1(const wchar_t *, size_t);
1219
char &i4 = L"foo"_x1; // ok
1320
double &i5 = R"(foo)"_x1; // ok
14-
double &i6 = u\
21+
char8 &i6 = u\
1522
8\
1623
R\
1724
"(foo)"\
1825
_\
1926
x\
2027
1; // ok
28+
29+
#if __cplusplus >= 202002L
30+
template<int N> struct S {
31+
char a[N];
32+
constexpr S(const char (&r)[N]) {
33+
__builtin_memcpy(a, r, N);
34+
if (a[0] == 'x') throw "no";
35+
}
36+
constexpr ~S() {
37+
if (a[0] == 'y') throw "also no";
38+
}
39+
};
40+
41+
// Check the produced contents are correct.
42+
template<S s> constexpr const decltype(s) &operator""_str() { return s; }
43+
static_assert(__builtin_strcmp("hello world"_str.a, "hello world") == 0);
44+
45+
template<S> float &operator""_s();
46+
void no_fallback() {
47+
"hello"_s;
48+
// FIXME: It'd be useful to explain what candidates were found and why they didn't work.
49+
"xyzzy"_s; // expected-error {{no matching literal operator for call to 'operator""_s' with arguments of types 'const char *' and 'unsigned long', and no matching literal operator template}}
50+
"yello"_s; // expected-error {{no matching literal operator for call to 'operator""_s' with arguments of types 'const char *' and 'unsigned long', and no matching literal operator template}}
51+
}
52+
53+
double &operator""_s(const char*, size_t);
54+
void f() {
55+
float &a = "foo"_s;
56+
double &b = "xar"_s;
57+
double &c = "yar"_s;
58+
}
59+
60+
template<S<4>> float &operator""_t();
61+
double &operator""_t(const char*, size_t);
62+
void g() {
63+
double &a = "fo"_t;
64+
float &b = "foo"_t;
65+
double &c = "fooo"_t;
66+
}
67+
68+
template<int N> struct X {
69+
static constexpr int size = N;
70+
constexpr X(const char (&r)[N]) {}
71+
};
72+
template<X x> requires (x.size == 4) // expected-note {{because 'X<5>{}.size == 4' (5 == 4) evaluated to false}}
73+
void operator""_x(); // expected-note {{constraints not satisfied}}
74+
void operator""_x(const char*, size_t) = delete;
75+
76+
template<int N> requires (N == 4)
77+
struct Y {
78+
constexpr Y(const char (&r)[N]) {}
79+
};
80+
template<Y> float &operator""_y();
81+
void operator""_y(const char*, size_t) = delete; // expected-note {{deleted here}}
82+
83+
void test() {
84+
"foo"_x;
85+
"foo"_y;
86+
87+
// We only check the template argument itself for validity, not the whole
88+
// call, when deciding whether to use the template or non-template form.
89+
"fooo"_x; // expected-error {{no matching function}}
90+
"fooo"_y; // expected-error {{deleted function}}
91+
}
92+
#endif

0 commit comments

Comments
 (0)