For the following code
struct X {
int a;
};
int main() {
X x;
return 0;
}
The clang AST does not show a DestructorDecl:
CXXRecordDecl 0x55a415f54f00 </home/gkxx/exercises/smfgen/tmp/../tmp/a.cpp:1:1, line:4:1> line:1:8 referenced struct X definition
|-DefinitionData pass_in_registers aggregate standard_layout trivially_copyable pod trivial literal
| |-DefaultConstructor exists trivial
| |-CopyConstructor simple trivial has_const_param implicit_has_const_param
| |-MoveConstructor exists simple trivial
| |-CopyAssignment simple trivial has_const_param needs_implicit implicit_has_const_param
| |-MoveAssignment exists simple trivial needs_implicit
| `-Destructor simple irrelevant trivial needs_implicit
|-CXXRecordDecl 0x55a415f55018 <col:1, col:8> col:8 implicit struct X
|-FieldDecl 0x55a415f550c0 <line:2:3, col:7> col:7 a 'int'
|-CXXConstructorDecl 0x55a415f55320 <line:1:8> col:8 implicit used X 'void () noexcept' inline default trivial
| `-CompoundStmt 0x55a415f557d8 <col:8>
|-CXXConstructorDecl 0x55a415f55458 <col:8> col:8 implicit constexpr X 'void (const X &)' inline default trivial noexcept-unevaluated 0x55a415f55458
| `-ParmVarDecl 0x55a415f55568 <col:8> col:8 'const X &'
`-CXXConstructorDecl 0x55a415f55618 <col:8> col:8 implicit constexpr X 'void (X &&)' inline default trivial noexcept-unevaluated 0x55a415f55618
`-ParmVarDecl 0x55a415f55728 <col:8> col:8 'X &&'
I tried to get the CXXRecordDecl of struct X and found that decl->getDestructor() returns nullptr.
This makes me a little bit confused, because the standard says that a class without a user-declared destructor will have an implicilty-declared one, and it will be implicitly-defined when odr-used. So what is an odr-used destructor? Is the destructor of this X odr-used? Or is this just about my misunderstanding of clang AST and the behavior of clang::CXXRecordDecl::getDestructor?
Note that in contrast, the clang-AST shows that the default constructor of X is defined, even though it does not do anything.
Is this destructor ODR-used?
Yes. Quoting basic.def.odr:
where "potentially invoked" has a non-trivial definition, but in short, is true here because
main()declares an object of typeX.What does it mean that Clang
getDestructor()isnullptr?The documentation of
CXXRecordDecl::getDestructor()just says:without even acknowledging that it can return
nullptr, let alone explaining what that would mean. Based on reading the source, I conclude it means the destructor is trivial, and that none of the conditions that would cause an implicit declaration to be created anyway (see below) have been satisfied.It does not mean that Clang is claiming the destructor is not ODR-used. The
Decl::isUsed()method claims to report this information (see documentation onsetIsUsed()), but it does not appear to be completely accurate; even if I cause the destructor declaration to be created by adding avirtualfunction, the destructor is still not markedisUsed().Doesn't the standard say the destructor should be defined here?
Yes--but to be conforming, Clang only needs to produce compiled output that acts "as if" the destructor had been defined. The absence of a particular AST node doesn't make it non-conforming. Even the lack of a definition in the compiled object file doesn't, so long as it adheres to the relevant ABI, thereby cooperating with other tools, to again achieve the required "as if" behavior (under the assumption that the source code complies with the One Definition Rule).
What are the conditions for Clang to create an implicit declaration?
This doesn't appear to be documented, so I tried to figure it out from the source. An implicit destructor declaration is created by
Sema::DeclareImplicitDestructor()atSemaDeclCXX.cpp:13803. This function is called in several places, and I didn't follow all of the chains backward, but to a first approximation, this will not be called if:requires).Note that it is that last condition, overload resolution due to the declaration of
x, that causes Clang to declare the three implicit constructors.Can I force the destructor to be declared anyway?
Yes! You can call
Sema::ForceDeclarationOfImplicitMemberson aCXXRecordDeclto force Clang to declare the implicit members even if it would not have done so otherwise. For instance, right after parsing, you can use aRecursiveASTVisitorto walk the AST and call this method on everyCXXRecordDecl.After doing that, all of the implicit members will be available.
How would I get a
Semaobject?You cannot get it from an
ASTContextbecause that is just the AST, whereasSemais part of the process for creating the AST. (If you were to save the AST to disk and then load it back, there would not be aSemaobject at all.)My preferred approach is to use
ASTUnit::LoadFromCompilerInvocationto do the initial parse. That yields anASTUnitobject immediately, from which you can get both theSema(viagetSema()) and theASTContext(viagetASTContext()).If you are instead using
ClangTool::run, which is what the tutorial points people at:Grab the
CompilerInstanceinCreateASTConsumer:Then store it in the
ASTConsumer, and use itsgetSema()when needed: