I have a tool built using clang that parses a C++ file, explicitly generates methods (e.g. copy constructors), and then prints the resulting AST to a file. This works for simple code, but when template instantiations start coming into play, I can not figure out a (reliable) way to get a handle on a mutable ClassTemplateSpecializationDecl
when a templated-class is instantiated.
I have set up an ASTConsumer
to notify me of each TagDecl
that is in the file and I generate its implicit methods using the Sema
object. Specifically, I generate the definitions in HandleTagDeclDefinition
and generate the from HandleTopLevelDecl
. To be notified when a template instantiation forces the generation of another template specialization, I need to use an ASTMutationListener
; however, the declarations passed to the callbacks on ASTMutationListener
are const
so I can not add the implicit members to them without doing a const_cast
. The logic is roughly:
class Impl : public clang::ASTConsumer, clang::ASTMutationListener {
public:
// Implementation of `clang::ASTConsumer`
virtual void HandleTagDeclDefinition(TagDecl *decl) override { elab(i); }
virtual void HandleTagDeclRequiredDefinition(const TagDecl *decl) override {
// [TagDecl] is [const], so I can't do anything here
}
virtual bool HandleTopLevelDecl(DeclGroupRef decl) override {
for (auto i : decl) {
elab(i, true);
}
}
virtual void HandleInlineFunctionDefinition(FunctionDecl *decl) override { elab(decl, true); }
virtual void
HandleCXXImplicitFunctionInstantiation(FunctionDecl *decl) override;
virtual ASTMutationListener *GetASTMutationListener() override {
return this;
}
public:
// Implementation of clang::ASTMutationListener
virtual void
AddedCXXTemplateSpecialization(const ClassTemplateDecl *TD,
const ClassTemplateSpecializationDecl *D) {
// The implementation calls this method from a non-`const` method.
// it is not clear (to me) why this method should take a
// `const ClassTemplateSpecializationDecl` rather than a non-`const`
elab(const_cast<ClassTemplateSpecializationDecl *>(D), true);
}
private:
void elab(Decl *, bool = false); // function that I need to call on every declaration/definition
};
This works in my test cases, and it seems (see comment) that the const_cast
is not UB according to C++ since the method that is calling this function is calling it on a mutable object; however, the need for a const_cast
is always concerning. I’m wondering if there is something in the API that I am missing? The documentation seems to suggest that ASTConsumer
is mostly meant for reading, though it does give back mutable pointers to the nodes. Is there some way that I can be notified of this with a mutable object so that I can perform the elaboration? Why does this method take a const
object?
As a note, I was previously using enableIncrementalProcessing
and the solution here (also my question), but that solution stopped working for me with clang16 with an error when I try to compile a file like:
class C { ~C(); };
C::~C() { }
Because it seems that clang16 thinks that C::~C()
is a call to a non-static member function rather than looking ahead and seeing that it is really the declaration of that function.