-
Notifications
You must be signed in to change notification settings - Fork 10.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
clang: calls redundant move-constructor when explicit constructor of a nested object is called in an aggregate #92495
Comments
@llvm/issue-subscribers-clang-frontend Author: Pavel Samolysov (samolisov)
**To be short**
When clang compiles the following minimum demonstration with an aggregate with a base class where the base class has an explicit constructor and therefore double The code: #include <cstdio>
struct A {
/*explicit */A(const char *) { printf("A(const char *)\n"); }
A(const A &) { printf("A(const A &)\n"); }
A(A&&) { printf("A(A&&)\n"); }
A &operator=(const A &) { printf("operator=(const A&)\n"); return *this; }
A &operator=(A &&) { printf("operator=(A&&)\n"); return *this; }
~A() { printf("~A()\n"); }
};
struct B : A {};
B rvo() {
return B{{"Hi"}};
}
B norvo() {
return B{A{"Hi2"}};
}
A rvoa() {
return {"hi"};
}
A norvoa() {
return A{"hi2"};
}
int main() {
{
printf("rvo:\n");
B b = rvo();
A a = rvoa();
}
{
printf("no rvo:\n");
B b = norvo();
A a = norvoa();
}
return 0;
} So, when an object of type Analysis The following AST was generated for the first case (
while the AST for the second case (
After some debugging, I've found the following code in the SemaInit.cpp file, which is responsible for adding a // C++17 [dcl.init]p17:
// - If the initializer expression is a prvalue and the cv-unqualified
// version of the source type is the same class as the class of the
// destination, the initializer expression is used to initialize the
// destination object.
// Per DR (no number yet), this does not apply when initializing a base
// class or delegating to another constructor from a mem-initializer.
// ObjC++: Lambda captured by the block in the lambda to block conversion
// should avoid copy elision.
if (S.getLangOpts().CPlusPlus17 && !RequireActualConstructor &&
UnwrappedArgs.size() == 1 && UnwrappedArgs[0]->isPRValue() &&
S.Context.hasSameUnqualifiedType(UnwrappedArgs[0]->getType(), DestType)) {
// Convert qualifications if necessary.
Sequence.AddQualificationConversionStep(DestType, VK_PRValue);
...
return;
}
... But this code works only for the first case because only in this case the // Add the constructor initialization step. Any cv-qualification conversion is
// subsumed by the initialization.
Sequence.AddConstructorInitializationStep(
Best->FoundDecl, CtorDecl, DestArrayType, HadMultipleCandidates,
IsListInit | IsInitListCopy, AsInitializerList); The bool RequireActualConstructor =
!(Entity.getKind() != InitializedEntity::EK_Base &&
Entity.getKind() != InitializedEntity::EK_Delegating &&
Entity.getKind() !=
InitializedEntity::EK_LambdaToBlockConversionBlockElement); In this case, we have This may look as a fix until we see the second part of the comment on line 4264:
I didn't manage to find the corresponding DR, as I see this is now (hm, now, I mean 7 years ago?) a part of the C++17 standard as well as all the subsequent versions and before changing anything in the code the question about legality of such change should be considered. But the difference in the behavior between clang and MSVC + gcc makes me wonder. |
To be short
When clang compiles the following minimum demonstration with an aggregate with a base class where the base class has an explicit constructor and therefore double
{}
cannot be used to make the aggregate's instances, a code to create a temporary object of the base class is generated by clang and then the move constructor is invoked redundancy to move the temporary into its correct place within the aggregate. I compared the code with the one generated by MSVC and g++, they do not insert a call for the move constructor (but to be clear, I've found a comment in the corresponding code of clang what unsure me who does the correct thing in this situation).The code:
So, when an object of type
B
is created usingB{{"Hi"}}
, everything is fine and no move-constructor forA
is invoked, but when the object ofB
is created usingB{A{"Hi2"}}
, the move constructor forA
is invoked.Analysis
The following AST was generated for the first case (
B{{"Hi"}}
):while the AST for the second case (
B{A{"Hi2"}}
) contains aMaterializeTemporaryExpr
expression:After some debugging, I've found the following code in the SemaInit.cpp file, which is responsible for adding a
QualificationConversion
step into the sequence of initialization actions:But this code works only for the first case because only in this case the
RequireActualConstructor
variable is set tofalse
. In the second case, with an explicit constructor call, the variable is set totrue
and no-early return occurs, the rest of thevoid TryConstructorInitialization()
function is executed what ends to the adding aConstructorInitialization
step into the sequence of initialization actions:The
RequireActualConstructor
variable is set by the following expression:bool RequireActualConstructor = !(Entity.getKind() != InitializedEntity::EK_Base && Entity.getKind() != InitializedEntity::EK_Delegating && Entity.getKind() != InitializedEntity::EK_LambdaToBlockConversionBlockElement);
In this case, we have
Entity.getKind()
equals toInitializedEntity::EK_Base
. When the decision for theRequireActualConstructor
variable is changed (this comparison was excluded), the observed behavior is changed too and no calling of the move constructor is generated anymore.This may look as a fix until we see the second part of the comment on line 4264:
I didn't manage to find the corresponding DR, as I see this is now (hm, now, I mean 7 years ago?) a part of the C++17 standard as well as all the subsequent versions and before changing anything in the code the question about legality of such change should be considered. But the difference in the behavior between clang and MSVC + gcc makes me wonder.
My concern is the following, in the part of creating
A
we are creating an instance ofA
, notB
and it is not clear whyEntity
is set toInitializedEntity::EK_Base
.The text was updated successfully, but these errors were encountered: