I've been working on a Clang Tool that aims to generate the implicitly-declared and implicitly-defined special member functions. I get the Clang AST, and I found that Clang does not always declare/define the functions that the standard requires to be implicitly-declared/defined. For example:
struct X {
int a;
};
int main() {
X x;
return 0;
}
According to the standard, X::~X() is odr-used and hence should be implicitly-defined (not only declared). Moreover, X::operator=(const X &) and X::operator=(X &&) should be implicitly-declared since there are no user-declared ones. However, none of these are represented in the Clang AST:
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 &&'
As for the special member functions, Clang AST only shows the three constructors. It seems that these three constructors are declared due to the need of overload resolution.
In this question I learned that Sema::ForceDeclarationOfImplicitMembers can be used to generate all the implicit declarations, but the destructor X::~X() still has only a declaration. I found that Sema has many related operations, named DefineImplicitxxxxxx, and they do define the required functions, but without checking whether it is odr-used. For example,
const clang::CompilerInstance *gCI;
struct MyVisitor : public clang::RecursiveASTVisitor<MyVisitor> {
bool VisitCXXRecordDecl(clang::CXXRecordDecl *classDecl) {
// Make sure all the special members have a declaration
gCI->getSema().ForceDeclarationOfImplicitMembers(classDecl);
auto dtor = classDecl->getDestructor();
gCI->getSema().DefineImplicitDestructor(dtor->getLocation(), dtor);
classDecl->dump();
// Even if the destructor is not odr-used, it is still defined now.
}
};
Decl::isUsed() claims to report whether the function is odr-used (hence needs a definition), but it is not accurate either. In the example above it still returns false for the destructor of X.
I also tried Sema::getUndefinedButUsed:
const clang::CompilerInstance *gCI;
struct MyVisitor : public clang::RecursiveASTVisitor<MyVisitor> {
bool VisitCXXRecordDecl(clang::CXXRecordDecl *classDecl) {
// Make sure all the special members have a declaration
gCI->getSema().ForceDeclarationOfImplicitMembers(classDecl);
llvm::SmallVector<std::pair<clang::NamedDecl *, clang::SourceLocation>> undefinedButUsed;
gCI->getSema().getUndefinedButUsed(undefinedButUsed);
llvm::outs() << undefinedButUsed.size() << '\n';
}
};
But the output is always 0 i.e. undefinedButUsed is empty.
I got really confused. Is there a way to make the special member functions truly conform to the standard, i.e. declared/defined as they are required to be? Or is there a reliable way to know whether it is odr-used, so that we can manually define them when needed?