Skip to content

Commit 3ebc138

Browse files
committed
Fixed #467: Forward from invoke to call operator
1 parent 95564d9 commit 3ebc138

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+745
-410
lines changed

CodeGenerator.cpp

Lines changed: 97 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -978,34 +978,56 @@ void CodeGenerator::InsertArg(const VarDecl* stmt)
978978

979979
bool CodeGenerator::InsertLambdaStaticInvoker(const CXXMethodDecl* cxxMethodDecl)
980980
{
981-
if(cxxMethodDecl && cxxMethodDecl->isLambdaStaticInvoker()) {
982-
mOutputFormatHelper.AppendNewLine();
981+
if(not(cxxMethodDecl and cxxMethodDecl->isLambdaStaticInvoker())) {
982+
return false;
983+
}
983984

984-
const auto* lambda = cxxMethodDecl->getParent();
985-
const auto* callOp = lambda->getLambdaCallOperator();
986-
if(lambda->isGenericLambda() && cxxMethodDecl->isFunctionTemplateSpecialization()) {
987-
const TemplateArgumentList* tal = cxxMethodDecl->getTemplateSpecializationArgs();
988-
FunctionTemplateDecl* callOpTemplate = callOp->getDescribedFunctionTemplate();
989-
void* insertPos = nullptr;
990-
FunctionDecl* correspondingCallOpSpecialization =
991-
callOpTemplate->findSpecialization(tal->asArray(), insertPos);
992-
callOp = cast<CXXMethodDecl>(correspondingCallOpSpecialization);
993-
}
985+
// A special case for a lambda with a static invoker. The standard says, that in such a case invoking the call
986+
// operator gives the same result as invoking the function pointer (see [expr.prim.lambda.closure] p9). When it
987+
// comes to block local statics having a body for both functions reveals a difference. This special code
988+
// generates a forwarding call from the call operator to the static invoker. However, the compiler does better
989+
// here. As this way we end up with copies of the parameters which is hard to avoid.
994990

995-
InsertArg(callOp->getBody());
996-
mOutputFormatHelper.AppendNewLine();
991+
mOutputFormatHelper.AppendNewLine();
992+
mOutputFormatHelper.OpenScope();
997993

998-
return true;
994+
if(not cxxMethodDecl->getReturnType()->isVoidType()) {
995+
mOutputFormatHelper.Append(kwReturn, " "sv);
999996
}
1000997

1001-
return false;
998+
mOutputFormatHelper.Append(GetName(*cxxMethodDecl->getParent()), "{}.operator()");
999+
1000+
if(cxxMethodDecl->isFunctionTemplateSpecialization()) {
1001+
InsertTemplateArgs(*dyn_cast_or_null<FunctionDecl>(cxxMethodDecl));
1002+
}
1003+
1004+
if(cxxMethodDecl->isTemplated()) {
1005+
if(cxxMethodDecl->getDescribedTemplate()) {
1006+
InsertTemplateParameters(*cxxMethodDecl->getDescribedTemplate()->getTemplateParameters(),
1007+
TemplateParamsOnly::Yes);
1008+
}
1009+
/*else if(decl.isFunctionTemplateSpecialization()) {
1010+
InsertTemplateSpecializationHeader();
1011+
}*/
1012+
}
1013+
1014+
WrapInParens([&] {
1015+
mOutputFormatHelper.AppendParameterList(cxxMethodDecl->parameters(),
1016+
OutputFormatHelper::NameOnly::Yes,
1017+
OutputFormatHelper::GenMissingParamName::Yes);
1018+
});
1019+
1020+
mOutputFormatHelper.AppendSemiNewLine();
1021+
mOutputFormatHelper.CloseScope(OutputFormatHelper::NoNewLineBefore::Yes);
1022+
mOutputFormatHelper.AppendNewLine();
1023+
1024+
return true;
10021025
}
10031026
//-----------------------------------------------------------------------------
10041027

10051028
/// \brief Inserts the instantiation point of a template.
10061029
//
10071030
// This reveals at which place the template is first used.
1008-
10091031
void CodeGenerator::InsertInstantiationPoint(const SourceManager& sm,
10101032
const SourceLocation& instLoc,
10111033
std::string_view text)
@@ -1172,9 +1194,16 @@ static std::string GetTypeConstraintAsString(const TypeConstraint* typeConstrain
11721194
}
11731195
//-----------------------------------------------------------------------------
11741196

1175-
void CodeGenerator::InsertTemplateParameters(const TemplateParameterList& list)
1197+
void CodeGenerator::InsertTemplateParameters(const TemplateParameterList& list,
1198+
const TemplateParamsOnly templateParamsOnly)
11761199
{
1177-
mOutputFormatHelper.Append(kwTemplate, "<"sv);
1200+
const bool full{TemplateParamsOnly::No == templateParamsOnly};
1201+
1202+
if(full) {
1203+
mOutputFormatHelper.Append(kwTemplate);
1204+
}
1205+
1206+
mOutputFormatHelper.Append("<"sv);
11781207

11791208
OnceFalse needsComma{};
11801209
for(const auto* param : list) {
@@ -1183,18 +1212,24 @@ void CodeGenerator::InsertTemplateParameters(const TemplateParameterList& list)
11831212
const auto& typeName = GetName(*param);
11841213

11851214
if(const auto* tt = dyn_cast_or_null<TemplateTypeParmDecl>(param)) {
1186-
if(tt->wasDeclaredWithTypename()) {
1187-
mOutputFormatHelper.Append(kwTypeNameSpace);
1188-
} else if(not tt->hasTypeConstraint()) {
1189-
mOutputFormatHelper.Append(kwClassSpace);
1190-
}
1215+
if(full) {
1216+
if(tt->wasDeclaredWithTypename()) {
1217+
mOutputFormatHelper.Append(kwTypeNameSpace);
1218+
} else if(not tt->hasTypeConstraint()) {
1219+
mOutputFormatHelper.Append(kwClassSpace);
1220+
}
11911221

1192-
if(tt->isParameterPack()) {
1193-
mOutputFormatHelper.Append(kwElipsisSpace);
1222+
if(tt->isParameterPack()) {
1223+
mOutputFormatHelper.Append(kwElipsisSpace);
1224+
}
11941225
}
11951226

11961227
if(0 == typeName.size() || tt->isImplicit() /* fixes class container:auto*/) {
1197-
AppendTemplateTypeParamName(mOutputFormatHelper, tt, false);
1228+
AppendTemplateTypeParamName(mOutputFormatHelper, tt, not full);
1229+
1230+
if(not full and tt->isParameterPack()) {
1231+
mOutputFormatHelper.Append(kwElipsis);
1232+
}
11981233

11991234
} else {
12001235
if(auto typeConstraint = GetTypeConstraintAsString(tt->getTypeConstraint());
@@ -1232,9 +1267,12 @@ void CodeGenerator::InsertTemplateParameters(const TemplateParameterList& list)
12321267
}
12331268
}
12341269

1235-
mOutputFormatHelper.AppendNewLine(">"sv);
1270+
mOutputFormatHelper.Append(">"sv);
12361271

1237-
InsertConceptConstraint(list);
1272+
if(full) {
1273+
mOutputFormatHelper.AppendNewLine();
1274+
InsertConceptConstraint(list);
1275+
}
12381276
}
12391277
//-----------------------------------------------------------------------------
12401278

@@ -3742,6 +3780,10 @@ void CodeGenerator::InsertFunctionNameWithReturnType(const FunctionDecl& d
37423780
const bool isClassTemplateSpec{isCXXMethodDecl && isa<ClassTemplateSpecializationDecl>(methodDecl->getParent())};
37433781
const bool requiresComment{isCXXMethodDecl && not methodDecl->isUserProvided() &&
37443782
not methodDecl->isExplicitlyDefaulted()};
3783+
// [expr.prim.lambda.closure] p7 consteval/constexpr are obtained from the call operator
3784+
const bool isLambdaStaticInvoker{isCXXMethodDecl and methodDecl->isLambdaStaticInvoker()};
3785+
const FunctionDecl& constExprDecl{not isLambdaStaticInvoker ? decl
3786+
: *methodDecl->getParent()->getLambdaCallOperator()};
37453787

37463788
if(methodDecl) {
37473789
if(requiresComment) {
@@ -3803,9 +3845,22 @@ void CodeGenerator::InsertFunctionNameWithReturnType(const FunctionDecl& d
38033845
}
38043846
}
38053847

3806-
if(decl.isConstexpr()) {
3807-
if(decl.isConstexprSpecified()) {
3808-
const bool skipConstexpr{isLambda};
3848+
if(constExprDecl.isConstexpr()) {
3849+
const bool skipConstexpr{isLambda and not isa<CXXConversionDecl>(constExprDecl)};
3850+
// Special treatment for a conversion operator in a captureless lambda. It appears that if the call operator is
3851+
// consteval the conversion operator must be as well, otherwise it cannot take the address of the invoke
3852+
// function.
3853+
const bool isConversionOpWithConstevalCallOp{[&]() {
3854+
if(methodDecl) {
3855+
if(const auto callOp = methodDecl->getParent()->getLambdaCallOperator()) {
3856+
return callOp->isConsteval();
3857+
}
3858+
}
3859+
3860+
return false;
3861+
}()};
3862+
3863+
if(not isConversionOpWithConstevalCallOp and constExprDecl.isConstexprSpecified()) {
38093864
if(skipConstexpr) {
38103865
mOutputFormatHelper.Append(kwCommentStart);
38113866
}
@@ -3816,7 +3871,7 @@ void CodeGenerator::InsertFunctionNameWithReturnType(const FunctionDecl& d
38163871
mOutputFormatHelper.Append(kwCCommentEndSpace);
38173872
}
38183873

3819-
} else if(decl.isConsteval()) {
3874+
} else if(isConversionOpWithConstevalCallOp or constExprDecl.isConsteval()) {
38203875
mOutputFormatHelper.Append(kwConstEvalSpace);
38213876
}
38223877
}
@@ -3847,7 +3902,7 @@ void CodeGenerator::InsertFunctionNameWithReturnType(const FunctionDecl& d
38473902
outputFormatHelper.Append(GetName(decl));
38483903
}
38493904

3850-
if(!isLambda && isFirstCxxMethodDecl && decl.isFunctionTemplateSpecialization()) {
3905+
if(isFirstCxxMethodDecl and decl.isFunctionTemplateSpecialization()) {
38513906
CodeGenerator codeGenerator{outputFormatHelper};
38523907
codeGenerator.InsertTemplateArgs(decl);
38533908
}
@@ -3861,7 +3916,14 @@ void CodeGenerator::InsertFunctionNameWithReturnType(const FunctionDecl& d
38613916
OutputFormatHelper::NameOnly::No,
38623917
OutputFormatHelper::GenMissingParamName::Yes);
38633918
} else {
3864-
outputFormatHelper.AppendParameterList(decl.parameters());
3919+
// The static invoker needs parameter names to foward parameters to the call operator even when the call
3920+
// operator doesn't care about them.
3921+
const OutputFormatHelper::GenMissingParamName genMissingParamName{
3922+
isLambdaStaticInvoker ? OutputFormatHelper::GenMissingParamName::Yes
3923+
: OutputFormatHelper::GenMissingParamName::No};
3924+
3925+
outputFormatHelper.AppendParameterList(
3926+
decl.parameters(), OutputFormatHelper::NameOnly::No, genMissingParamName);
38653927
}
38663928

38673929
if(decl.isVariadic()) {

CodeGenerator.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,10 @@ class CodeGenerator
256256

257257
void InsertTemplateArg(const TemplateArgumentLoc& arg) { InsertTemplateArg(arg.getArgument()); }
258258
bool InsertLambdaStaticInvoker(const CXXMethodDecl* cxxMethodDecl);
259-
void InsertTemplateParameters(const TemplateParameterList& list);
259+
260+
STRONG_BOOL(TemplateParamsOnly); ///! Skip template, type constraints and class/typename.
261+
void InsertTemplateParameters(const TemplateParameterList& list,
262+
const TemplateParamsOnly templateParamsOnly = TemplateParamsOnly::No);
260263

261264
STRONG_BOOL(InsertInline);
262265

OutputFormatHelper.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ void OutputFormatHelper::AppendParameterList(const ArrayRef<ParmVarDecl*> parame
4646
Append(GetTypeNameAsParameter(type, name));
4747
} else {
4848
Append(name);
49+
50+
if(isa<PackExpansionType>(p->getType())) {
51+
Append(kwElipsis);
52+
}
4953
}
5054
});
5155
}

docs/Limitations.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,29 @@ instantiated and the end of its enclosing namespace. While technically, this inf
2121
this point.
2222

2323

24+
## Lambdas with static invoker
25+
26+
### Captureless lambdas
27+
28+
Issue [#467](https://github.com/andreasfertig/cppinsights/issues/467) raised awareness that captureless lambdas are
29+
more complicated. According to [expr.prim.lambda.closure] p7, the closure type has a conversion to a function pointer
30+
function:
31+
32+
> The value returned by this conversion function is the address of a function F that, when invoked, has the same effect as invoking the
33+
> closure type’s function call operator on a default-constructed instance of the closure type. F is a
34+
> constexpr function if the function call operator is a constexpr function and is an immediate function if the function call operator is an immediate function.
35+
36+
The code in #467 demonstrates a way to observe that C++ Insights generates less efficient code. Initially, the body of
37+
the call operator was replicated into the invoke function. However, a captureless lambda with a local `static` variable leads to
38+
different results. The latest version forwards the call from the invoke function to the call operator. This is still
39+
less optimal as the compiler does it. Even with `move` and `forward`, we get copies of non-moveable members in a parameter.
40+
At the same time, the compiler seems to be able to directly forward the invoke call to the call operator.
41+
42+
43+
### Lambda captures initialization
44+
45+
C++ Insights shows a constructor for a lambda when it has captures. The compiler does better. It doesn't need a
46+
constructor, it can direct-initialize the members, and by that, the compiler reduces potential copies as they will happen
47+
with the C++ Insights version.
48+
49+

tests/Basic.Start.Main.P5.3Test.expect

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ int main()
1010
}
1111

1212
using retType_3_13 = auto (*)() -> int;
13-
inline /*constexpr */ operator retType_3_13 () const noexcept
13+
inline constexpr operator retType_3_13 () const noexcept
1414
{
1515
return __invoke;
1616
};
1717

1818
private:
19-
static inline int __invoke()
19+
static inline /*constexpr */ int __invoke()
2020
{
21-
return 2;
21+
return __lambda_3_13{}.operator()();
2222
}
2323

2424

tests/Basic.Start.Main.P5.4Test.expect

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ int main(int argc, const char ** argv)
1414
}
1515

1616
using retType_5_13 = auto (*)() -> int;
17-
inline /*constexpr */ operator retType_5_13 () const noexcept
17+
inline constexpr operator retType_5_13 () const noexcept
1818
{
1919
return __invoke;
2020
};
2121

2222
private:
23-
static inline int __invoke()
23+
static inline /*constexpr */ int __invoke()
2424
{
25-
return 2;
25+
return __lambda_5_13{}.operator()();
2626
}
2727

2828

tests/ClassOperatorHandler2Test.expect

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -104,36 +104,15 @@ int main()
104104
}
105105

106106
using retType_60_5 = void (*)();
107-
inline /*constexpr */ operator retType_60_5 () const noexcept
107+
inline constexpr operator retType_60_5 () const noexcept
108108
{
109109
return __invoke;
110110
};
111111

112112
private:
113113
static inline void __invoke()
114114
{
115-
int n = 10;
116-
f(n);
117-
f(42);
118-
f(int{});
119-
Foo f = Foo(1);
120-
f.operator++();
121-
f.operator++(0);
122-
f.operator--();
123-
f.operator--(0);
124-
printf("%d\n", f.operator++(0));
125-
printf("%d %d\n", f.operator++(0), 1);
126-
printf("%d\n", f.operator--(0));
127-
printf("%d %d\n", f.operator--(0), 1);
128-
Foo * ff = &f;
129-
printf("%d\n", ff->Get());
130-
++ff;
131-
ff++;
132-
++++ff;
133-
--ff;
134-
ff--;
135-
----ff;
136-
printf("%d\n", f.Get());
115+
__lambda_60_5{}.operator()();
137116
}
138117

139118

tests/ConstevalTest.expect

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,15 @@ class __lambda_16_15
3333
}
3434

3535
using retType_16_15 = bool (*)();
36-
inline /*constexpr */ operator retType_16_15 () const noexcept
36+
inline consteval operator retType_16_15 () const noexcept
3737
{
3838
return __invoke;
3939
};
4040

4141
private:
42-
static inline bool __invoke()
42+
static inline consteval bool __invoke()
4343
{
44-
return true;
44+
return __lambda_16_15{}.operator()();
4545
}
4646

4747

tests/ConstraintAutoParameterTest.expect

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,16 @@ class __lambda_6_15
1414

1515
#ifdef INSIGHTS_USE_TEMPLATE
1616
template<>
17-
inline /*constexpr */ void operator()(int container) const
17+
inline /*constexpr */ void operator()<int>(int container) const
1818
{
1919
}
2020
#endif
2121

2222
private:
2323
template<C type_parameter_0_0>
24-
static inline auto __invoke(type_parameter_0_0 container)
24+
static inline /*constexpr */ auto __invoke(type_parameter_0_0 container)
2525
{
26+
return __lambda_6_15{}.operator()<type_parameter_0_0>(container);
2627
}
2728

2829
public:

tests/EmptyLambda.expect

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@ void foo()
99
}
1010

1111
using retType_2_1 = void (*)();
12-
inline /*constexpr */ operator retType_2_1 () const noexcept
12+
inline constexpr operator retType_2_1 () const noexcept
1313
{
1414
return __invoke;
1515
};
1616

1717
private:
18-
static inline void __invoke()
18+
static inline /*constexpr */ void __invoke()
1919
{
20+
__lambda_2_1{}.operator()();
2021
}
2122

2223

0 commit comments

Comments
 (0)