Information about deleted implicit constructors is available after using
the ForceDeclarationOfImplicitMembers
of the
Sema
(semantic analysis) class. That will populate the
CXXRecordDecl
with
CXXConstructorDecl
nodes, which can then be iterated over with CXXRecordDecl::decls
or visited using a
RecursiveASTVisitor
.
One tricky bit is getting ahold of the Sema
object in order to
invoke the required method. If you are using
the LoadFromCommandLine
method of
ASTUnit
,
that returns an ASTUnit
object, which has a getSema
method.
If you are using
ClangTool
,
you can either use buildASTs
to get ASTUnit
s directly,
or use run
with a
FrontendAction
and use its getCurrentASTUnit
method.
(The tutorial
makes this somewhat difficult because it uses
newFrontendActionFactory
. Dealing with that complication could be the
subject of a new question.)
In either case, once the AST nodes for the implicit methods have been
created, the isDeleted
method
(declared on FunctionDecl
)
will return true
if the method has been implicitly or explicitly
deleted.
Finally, be aware that if you use a RecursiveASTVisitor
to visit the
methods, by default it skips implicitly-defined methods. To change
that, implement the shouldVisitImplicitCode
method and have it return
true
.
Complete example
Below are sources and tests to make and run a program that report all
constructors, including those that are implicit, and whether they are
deleted.
deleted-ctor.cc
:
// deleted-ctor.cc
// Report deleted constructors.
#include "clang/AST/RecursiveASTVisitor.h" // clang::RecursiveASTVisitor
#include "clang/Basic/Diagnostic.h" // clang::DiagnosticsEngine
#include "clang/Basic/DiagnosticOptions.h" // clang::DiagnosticOptions
#include "clang/Frontend/ASTUnit.h" // clang::ASTUnit
#include "clang/Frontend/CompilerInstance.h" // clang::CompilerInstance
#include "clang/Sema/Sema.h" // clang::Sema
#include "clang/Serialization/PCHContainerOperations.h" // clang::PCHContainerOperations
#include <iostream> // std::cout
#include <string> // std::string
using std::cout;
using std::string;
class Visitor : public clang::RecursiveASTVisitor<Visitor> {
public: // data
clang::ASTUnit *m_astUnit;
clang::ASTContext &m_astContext;
public: // methods
Visitor(clang::ASTUnit *astUnit)
: m_astUnit(astUnit),
m_astContext(astUnit->getASTContext())
{}
// Convenience methods to stringify some things.
string locStr(clang::SourceLocation loc);
string declLocStr(clang::Decl const *decl);
string typeStr(clang::QualType qualType);
// By default, AST visitors skip implicit nodes even if they are
// present in the AST. Returning true here ensures we will not skip
// them.
bool shouldVisitImplicitCode() { return true; }
// Visitor methods.
bool VisitCXXRecordDecl(clang::CXXRecordDecl *recordDecl);
bool VisitCXXConstructorDecl(clang::CXXConstructorDecl *methodDecl);
// Kick off the traversal.
void traverseTU();
};
string Visitor::locStr(clang::SourceLocation loc)
{
return loc.printToString(m_astContext.getSourceManager());
}
string Visitor::declLocStr(clang::Decl const *decl)
{
return locStr(decl->getLocation());
}
string Visitor::typeStr(clang::QualType qualType)
{
return qualType.getAsString();
}
bool Visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *recordDecl)
{
if (recordDecl->getDefinition() != recordDecl) {
// This is not the definition, so ignore it.
}
else {
cout << recordDecl->Decl::getDeclKindName()
<< " \"" << recordDecl->getQualifiedNameAsString()
<< "\" at " << declLocStr(recordDecl)
<< ":\n";
// Without this, the implicit members are not in the AST.
m_astUnit->getSema().ForceDeclarationOfImplicitMembers(recordDecl);
}
return true;
}
bool Visitor::VisitCXXConstructorDecl(clang::CXXConstructorDecl *ctor)
{
cout << " " << ctor->Decl::getDeclKindName()
<< " \"" << ctor->getQualifiedNameAsString()
<< "\" type=\"" << typeStr(ctor->getType())
<< "\" at "
<< declLocStr(ctor)
<< ": deleted=" << ctor->isDeleted()
<< "\n";
return true;
}
void Visitor::traverseTU()
{
this->TraverseDecl(m_astContext.getTranslationUnitDecl());
}
int main(int argc, char const **argv)
{
// Point 'LoadFromCommandLine' at the clang binary so it will be able
// to find its compiler headers such as stddef.h.
argv[0] = CLANG_LLVM_INSTALL_DIR "/bin/clang";
// Boilerplate setup for 'LoadFromCommandLine'.
std::shared_ptr<clang::PCHContainerOperations> pchContainerOps(
new clang::PCHContainerOperations());
clang::IntrusiveRefCntPtr<clang::DiagnosticsEngine> diagnosticsEngine(
clang::CompilerInstance::createDiagnostics(
new clang::DiagnosticOptions));
// Run the Clang parser to produce an AST.
std::unique_ptr<clang::ASTUnit> ast(clang::ASTUnit::LoadFromCommandLine(
argv,
argv + argc,
pchContainerOps,
diagnosticsEngine,
llvm::StringRef() /*ResourceFilesPath, evidently ignored*/));
if (ast == nullptr ||
diagnosticsEngine->getNumErrors() > 0) {
// Error messages have already been printed.
return 2;
}
Visitor visitor(ast.get());
visitor.traverseTU();
return 0;
}
// EOF
Makefile
:
# Makefile
# Default target.
all:
.PHONY: all
# ---- Configuration ----
# Installation directory from a binary distribution.
# Has five subdirectories: bin include lib libexec share.
# Downloaded from: https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.0/clang+llvm-14.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz
CLANG_LLVM_INSTALL_DIR = $(HOME)/opt/clang+llvm-14.0.0-x86_64-linux-gnu-ubuntu-18.04
# ---- llvm-config query results ----
# Program to query the various LLVM configuration options.
LLVM_CONFIG := $(CLANG_LLVM_INSTALL_DIR)/bin/llvm-config
# C++ compiler options to ensure ABI compatibility.
LLVM_CXXFLAGS := $(shell $(LLVM_CONFIG) --cxxflags)
# Directory containing the clang library files, both static and dynamic.
LLVM_LIBDIR := $(shell $(LLVM_CONFIG) --libdir)
# Other flags needed for linking, whether statically or dynamically.
LLVM_LDFLAGS_AND_SYSTEM_LIBS := $(shell $(LLVM_CONFIG) --ldflags --system-libs)
# ---- Compiler options ----
# C++ compiler.
#CXX = g++
CXX := $(CLANG_LLVM_INSTALL_DIR)/bin/clang++
# Compiler options, including preprocessor options.
CXXFLAGS =
CXXFLAGS += -Wall
CXXFLAGS += -Werror
# Silence a warning about a multi-line comment in DeclOpenMP.h.
CXXFLAGS += -Wno-comment
# Get llvm compilation flags.
CXXFLAGS += $(LLVM_CXXFLAGS)
# Tell the source code where the clang installation directory is.
CXXFLAGS += -DCLANG_LLVM_INSTALL_DIR='"$(CLANG_LLVM_INSTALL_DIR)"'
# Linker options.
LDFLAGS =
# Pull in clang+llvm via libclang-cpp.so, which has everything, but is
# only available as a dynamic library.
LDFLAGS += -lclang-cpp
# Arrange for the compiled binary to search the libdir for that library.
# Otherwise, one can set the LD_LIBRARY_PATH envvar before running it.
# Note: the -rpath switch does not work on Windows.
LDFLAGS += -Wl,-rpath=$(LLVM_LIBDIR)
# Get the needed -L search path, plus things like -ldl.
LDFLAGS += $(LLVM_LDFLAGS_AND_SYSTEM_LIBS)
# ---- Recipes ----
# Compile a C++ source file.
%.o: %.cc
$(CXX) -c -o $@ $(CXXFLAGS) $<
# Executable.
all: deleted-ctor.exe
deleted-ctor.exe: deleted-ctor.o
$(CXX) -g -Wall -o $@ $^ $(LDFLAGS)
# Test.
.PHONY: run
run: deleted-ctor.exe
./deleted-ctor.exe test.cc
.PHONY: clean
clean:
$(RM) *.o *.exe
# EOF
test.cc
:
// test.cc
// Test for deleted-ctor.exe.
// Explicit ctor, not deleted.
class END {
public:
END();
};
// Explicit ctor, deleted.
class ED {
public:
ED()=delete;
};
// Implicit ctor, not deleted.
class IND {
public:
};
// Implicit ctor, deleted.
class ID {
public:
int &i;
};
// EOF
Example run:
$ make run
./deleted-ctor.exe test.cc
CXXRecord "END" at test.cc:5:7:
CXXConstructor "END::END" type="void (void)" at test.cc:7:3: deleted=0
CXXConstructor "END::END" type="void (const class END &)" at test.cc:5:7: deleted=0
CXXConstructor "END::END" type="void (class END &&)" at test.cc:5:7: deleted=0
CXXRecord "ED" at test.cc:11:7:
CXXConstructor "ED::ED" type="void (void)" at test.cc:13:3: deleted=1
CXXConstructor "ED::ED" type="void (const class ED &)" at test.cc:11:7: deleted=0
CXXConstructor "ED::ED" type="void (class ED &&)" at test.cc:11:7: deleted=0
CXXRecord "IND" at test.cc:18:7:
CXXConstructor "IND::IND" type="void (void)" at test.cc:18:7: deleted=0
CXXConstructor "IND::IND" type="void (const class IND &)" at test.cc:18:7: deleted=0
CXXConstructor "IND::IND" type="void (class IND &&)" at test.cc:18:7: deleted=0
CXXRecord "ID" at test.cc:23:7:
CXXConstructor "ID::ID" type="void (void)" at test.cc:23:7: deleted=1
CXXConstructor "ID::ID" type="void (const class ID &)" at test.cc:23:7: deleted=0
CXXConstructor "ID::ID" type="void (class ID &&)" at test.cc:23:7: deleted=0
Observe that the first constructor for each of ED
and ID
is labeled
with deleted=1
.