我有一个小代码,可以将 C++ 声明解析为类、模板类、函数等。概念,然后我想写下类的名称和概念的名称,这段代码表示该类对于概念的要求是否有效,如果不是,则说明哪些要求不满意。
这是我尝试实现的函数,但它不起作用,它总是返回 false,即使该类满足概念要求。所以我想找到一些如何使用 Clang Sema 分析概念的示例:
void CheckConceptUsage(Sema &SemaRef, const ConceptDecl *Concept, const CXXRecordDecl *Class) {
// Get the type of the class.
QualType ClassType = SemaRef.Context.getRecordType(Class);
// Create a TemplateArgument representing the class type.
TemplateArgument ClassTemplateArg(ClassType);
// Prepare the necessary data structures for the constraint check.
ConstraintSatisfaction Satisfaction;
// Create a MultiLevelTemplateArgumentList
MultiLevelTemplateArgumentList TemplateArgs;
ArrayRef<TemplateArgument> TemplateArgsRef(ClassTemplateArg);
TemplateArgs.addOuterTemplateArguments(const_cast<CXXRecordDecl *>(Class), TemplateArgsRef, /*Final*/ true);
// TemplateArgs.addOuterTemplateArguments(ArrayRef<TemplateArgument>(ClassTemplateArg));
// Retrieve the constraint expression associated with the concept
const Expr *ConstraintExpr = Concept->getConstraintExpr();
if (!ConstraintExpr) {
llvm::outs() << "The concept " << Concept->getNameAsString()
<< " has no constraints (requires clause) to check.\n";
return;
}
// Cast the constraint expression to RequiresExpr to access its components
if (const RequiresExpr *ReqExpr = llvm::dyn_cast<RequiresExpr>(ConstraintExpr)) {
std::cout << "--- CheckConceptUsage if " << std::endl;
// Get the list of requirements (constraints) in the requires expression
llvm::SmallVector<const Expr*, 4> ConstraintExprs;
for (const auto &Requirement : ReqExpr->getRequirements()) {
std::cout << "--- CheckConceptUsage for " << std::endl;
if (const auto *ExprReq = llvm::dyn_cast<clang::concepts::ExprRequirement>(Requirement)) {
// Handle expression requirements
std::cout << "--- CheckConceptUsage ExprRequirement" << std::endl;
ConstraintExprs.push_back(ExprReq->getExpr());
} else if (const auto *TypeReq = llvm::dyn_cast<clang::concepts::TypeRequirement>(Requirement)) {
// Handle type requirements by evaluating the type's instantiation dependency
std::cout << "--- CheckConceptUsage TypeRequirement" << std::endl;
QualType Type = TypeReq->getType()->getType();
QualType DependentType = TypeReq->getType()->getType();
if (Type->isDependentType()) {
std::cout << "--- CheckConceptUsage isDependentType" << std::endl;
// Create a pseudo-expression that checks if this type exists
// TypeTraitExpr *TraitExpr = TypeTraitExpr::Create(
// SemaRef.Context,
// DependentType,
// SourceLocation(),
// UTT_IsCompleteType, // Use a type trait like "is complete type"
// ArrayRef<QualType>(DependentType),
// SourceLocation(),
// SemaRef.Context.BoolTy
// );
// ConstraintExprs.push_back(TraitExpr);
}
}
}
std::cout << "--- CheckConceptUsage ConstraintExprs size:" << ConstraintExprs.size() << std::endl;
// Now use the updated list of constraints in the satisfaction check
bool IsSatisfied = SemaRef.CheckConstraintSatisfaction(
Concept,
ConstraintExprs,
TemplateArgs,
Class->getSourceRange(),
Satisfaction
);
if (IsSatisfied) {
llvm::outs() << "The class " << Class->getName() << " satisfies the concept " << Concept->getName() << ".\n";
} else {
llvm::outs() << "The class " << Class->getName() << " does NOT satisfy the concept " << Concept->getName() << ".\n";
}
} else {
llvm::outs() << "The concept " << Concept->getNameAsString()
<< " does not have a valid requires expression.\n";
}
}
这是我最小的可重现示例。
https://gist.github.com/alexst07/7dadf36ea663171e91778a77d01fbbda
原代码在 要点 离工作很近了。 我能够让它在给定的情况下工作 例如,另一个简单的例子,有一些变化。
CheckConstraintSatisfaction
返回值Sema::CheckConstraintSatisfaction
回报:
如果发生错误且无法检查满意度,则为 true,否则为 false。
即表示是否可以进行满意度检查,而不是 是否满足约束条件。
因此,该行:
bool IsSatisfied = SemaRef.CheckConstraintSatisfaction(
应该是这样的:
bool HadError = SemaRef.CheckConstraintSatisfaction(
...
);
if (HadError) {
llvm::outs() << "CheckConstraintSatisfaction reported an error.\n";
return;
}
我不确定此调用到底是什么构成错误。 在我的 测试,
HadError
始终为假。
然后,要检查是否满意,请查看 的
IsSatisfied
标志
Satisfaction
对象:
if (Satisfaction.IsSatisfied) {
llvm::outs() << "The class " << Class->getName() << " satisfies the concept " << Concept->getName() << ".\n";
} else {
llvm::outs() << "The class " << Class->getName() << " does NOT satisfy the concept " << Concept->getName() << ".\n";
}
您在评论中说您尝试过这样做;其他问题可能被掩盖 这个修复。
TypeTraitExpr
TypeRequirement
,
原始代码有一个被注释掉的尝试来创建一个
TypeTraitExpr
然后可以传递给 CheckConstraintSatisfaction
:
if (Type->isDependentType()) {
std::cout << "--- CheckConceptUsage isDependentType" << std::endl;
// Create a pseudo-expression that checks if this type exists
// TypeTraitExpr *TraitExpr = TypeTraitExpr::Create(
// SemaRef.Context,
// DependentType,
// SourceLocation(),
// UTT_IsCompleteType, // Use a type trait like "is complete type"
// ArrayRef<QualType>(DependentType),
// SourceLocation(),
// SemaRef.Context.BoolTy
// );
// ConstraintExprs.push_back(TraitExpr);
}
由于这已被注释掉,并且该示例使用两种类型 要求,
IsSatisfied
标志始终为真,因为没有
约束已传递给它。
取消注释代码后,并没有编译,而是一些小 更改足以修复它:
if (Type->isDependentType()) {
std::cout << "--- CheckConceptUsage isDependentType" << std::endl;
// Create a pseudo-expression that checks if this type exists
TypeTraitExpr *TraitExpr = TypeTraitExpr::Create(
SemaRef.Context,
DependentType,
SourceLocation(),
UTT_IsCompleteType, // Use a type trait like "is complete type"
ArrayRef<TypeSourceInfo*>(TypeReq->getType()),
SourceLocation(),
false /* Value; meaning is unclear */
);
ConstraintExprs.push_back(TraitExpr);
}
具体:
TypeSourceInfo
,
不是QualType
。
前者具有源位置信息和其他相关详细信息
类型描述的语法出现,而后者是
只是抽象语义类型。 TypeSourceInfo
就在那里
在 TypeRequirement
;原来的代码是出于某种原因
绕过它以获得QualType
。
最后一个参数是一个普通的
bool
,而不是一个类型(这就是
BoolTy
是)。 这是什么意思? 追踪代码,最终结果是
TypeTraitExprBitFields.Value
,其评论是“如果这个表达式
不依赖于值,这表明该特征是否被评估
真还是假。”我认为这在这里无关紧要,因为我们还没有
尚未评估特征表达,在我的实验中它没有产生
我传递的值不同。
话虽如此,我实际上不确定构建一个
TypeTraitExpr
确实是正确的方法,因为它看起来相当
令人费解。 但它有效,至少对于这些简单的情况,所以我没有
调查是否有更好的替代方案。
原始代码仅检查
SimpleClass
与 HasValueType
。
这使得很难判断它是否有效,因为代码应该
认识到 SimpleClass
确实满足约束条件,但 X
确实满足
不是。 所以我添加了一个简单的嵌套循环来检查所有对:
std::cout << "Testing all classes against all concepts.\n";
for (auto const &kv1 : ConceptMap) {
ConceptDecl const *concept = kv1.second;
std::cout << "- concept " << concept->getNameAsString() << ":\n";
for (auto const &kv2 : ClassMap) {
clang::CXXRecordDecl const *clazz = kv2.second;
std::cout << "-- class " << clazz->getNameAsString() << ":\n";
CheckConceptUsage(SemaRef, concept, clazz);
}
}
通过上述修复,程序可以在
example.cpp
上运行
链接要点(它应该直接在问题中):
template<typename T>
concept HasValueType = requires {
typename T::value_type;
typename T::test;
};
template <class myType>
myType GetMax (myType a, int b) {
return (a>b?a:b);
}
template<class T>
class A {
A(){}
using value_type = int;
int func_A1(T a, int b) {
return 5;
}
};
class SimpleClass {
public:
using value_type = int; // This is the nested type alias that satisfies the concept
using test = float;
};
template<class T>
class B {
B(){}
int func_A1(T a, int b) {
return 5;
}
};
class X{};
float test(A<float> x, float b, A<B<int>> y) {
return 4.0;
}
template<class T>
float test2(A<T> x, float b, A<B<T>> y) {
return 4.0;
}
int main() {
return 0;
}
example.cpp
的输出摘录:
...
The class SimpleClass satisfies the concept HasValueType.
...
The class X does NOT satisfy the concept HasValueType.
该输出是正确的,因为
SimpleClass
有 value_type
和 test
成员类型,而 X
则不然。
我也测试了这个输入,
example2.cpp
:
// Simple concept that requires an `asString` method.
template <typename T>
concept HasAsString = requires(T v)
{
v.asString();
};
// Does not have `asString`.
struct A {};
// Has `asString`.
struct B {
void asString();
};
// Requires the concept to be satisfied.
template <typename T>
requires HasAsString<T>
struct S {};
// Does not compile due to unsatisfied constraint.
//S<A> sa;
static_assert(!HasAsString<A>);
// Compiles since the constraint is satisfied.
S<B> sb;
static_assert(HasAsString<B>);
其输出摘录为:
...
The class A does NOT satisfy the concept HasAsString.
...
The class B satisfies the concept HasAsString.
这也是正确的,因为只有
B
有 asString()
。
这是链接要点中的完整程序,并应用了修复:
#include <clang/Tooling/CommonOptionsParser.h>
#include <clang/Tooling/Tooling.h>
#include <clang/Frontend/CompilerInstance.h>
#include <clang/Frontend/FrontendActions.h>
#include <clang/AST/ASTConsumer.h>
#include <clang/AST/RecursiveASTVisitor.h>
#include <clang/AST/Decl.h>
#include <clang/AST/DeclTemplate.h>
#include <llvm/Support/CommandLine.h>
#include <clang/Sema/Sema.h>
#include <clang/Sema/Template.h>
#include <clang/AST/ASTContext.h>
#include <iostream>
using namespace clang;
using namespace clang::tooling;
using namespace llvm;
// Define the option category for command-line options
static llvm::cl::OptionCategory MyToolCategory("my-tool options");
std::map<std::string, const CXXRecordDecl*> ClassMap;
std::map<std::string, const ConceptDecl*> ConceptMap;
void CheckConceptUsage(Sema &SemaRef, const ConceptDecl *Concept, const CXXRecordDecl *Class) {
// Get the type of the class.
QualType ClassType = SemaRef.Context.getRecordType(Class);
// Create a TemplateArgument representing the class type.
TemplateArgument ClassTemplateArg(ClassType);
// Prepare the necessary data structures for the constraint check.
ConstraintSatisfaction Satisfaction;
// Create a MultiLevelTemplateArgumentList
MultiLevelTemplateArgumentList TemplateArgs;
ArrayRef<TemplateArgument> TemplateArgsRef(ClassTemplateArg);
TemplateArgs.addOuterTemplateArguments(const_cast<CXXRecordDecl *>(Class), TemplateArgsRef, /*Final*/ true);
// TemplateArgs.addOuterTemplateArguments(ArrayRef<TemplateArgument>(ClassTemplateArg));
// Retrieve the constraint expression associated with the concept
const Expr *ConstraintExpr = Concept->getConstraintExpr();
if (!ConstraintExpr) {
llvm::outs() << "The concept " << Concept->getNameAsString()
<< " has no constraints (requires clause) to check.\n";
return;
}
// Cast the constraint expression to RequiresExpr to access its components
if (const RequiresExpr *ReqExpr = llvm::dyn_cast<RequiresExpr>(ConstraintExpr)) {
std::cout << "--- CheckConceptUsage if " << std::endl;
// Get the list of requirements (constraints) in the requires expression
llvm::SmallVector<const Expr*, 4> ConstraintExprs;
for (const auto &Requirement : ReqExpr->getRequirements()) {
std::cout << "--- CheckConceptUsage for " << std::endl;
if (const auto *ExprReq = llvm::dyn_cast<clang::concepts::ExprRequirement>(Requirement)) {
// Handle expression requirements
std::cout << "--- CheckConceptUsage ExprRequirement" << std::endl;
ConstraintExprs.push_back(ExprReq->getExpr());
} else if (const auto *TypeReq = llvm::dyn_cast<clang::concepts::TypeRequirement>(Requirement)) {
// Handle type requirements by evaluating the type's instantiation dependency
std::cout << "--- CheckConceptUsage TypeRequirement" << std::endl;
QualType Type = TypeReq->getType()->getType();
QualType DependentType = TypeReq->getType()->getType();
if (Type->isDependentType()) {
std::cout << "--- CheckConceptUsage isDependentType" << std::endl;
// Create a pseudo-expression that checks if this type exists
TypeTraitExpr *TraitExpr = TypeTraitExpr::Create(
SemaRef.Context,
DependentType,
SourceLocation(),
UTT_IsCompleteType, // Use a type trait like "is complete type"
ArrayRef<TypeSourceInfo*>(TypeReq->getType()),
SourceLocation(),
false /* Value; meaning is unclear */
);
ConstraintExprs.push_back(TraitExpr);
}
}
}
std::cout << "--- CheckConceptUsage ConstraintExprs size:" << ConstraintExprs.size() << std::endl;
// Now use the updated list of constraints in the satisfaction check
//
// Quoting the documentation of the return value:
//
// "true if an error occurred and satisfaction could not be
// checked, false otherwise."
//
bool HadError = SemaRef.CheckConstraintSatisfaction(
Concept,
ConstraintExprs,
TemplateArgs,
Class->getSourceRange(),
Satisfaction
);
if (HadError) {
llvm::outs() << "CheckConstraintSatisfaction reported an error.\n";
return;
}
llvm::outs() << "ContainsErrors: " << Satisfaction.ContainsErrors << "\n";
if (Satisfaction.IsSatisfied) {
llvm::outs() << "The class " << Class->getName() << " satisfies the concept " << Concept->getName() << ".\n";
} else {
llvm::outs() << "The class " << Class->getName() << " does NOT satisfy the concept " << Concept->getName() << ".\n";
}
} else {
llvm::outs() << "The concept " << Concept->getNameAsString()
<< " does not have a valid requires expression.\n";
}
}
class FunctionConsumer : public ASTConsumer {
public:
explicit FunctionConsumer(CompilerInstance &CI): CI(CI) {}
virtual void HandleTranslationUnit(ASTContext &Context) override {
TranslationUnitDecl *TU = Context.getTranslationUnitDecl();
this->Context = &Context;
Sema &SemaRef = CI.getSema();
for (Decl *D : TU->decls()) {
printDeclKind(D);
if (CXXRecordDecl *CRD = llvm::dyn_cast<CXXRecordDecl>(D)) {
// Handle classes/structs and their methods
handleClass(CRD);
} else if (ConceptDecl *CD = llvm::dyn_cast<ConceptDecl>(D)) {
// Handle template classes
std::cout << "--- CONCEPT: " << std::endl;
printConceptDetails(CD, &Context);
std::cout << "--- END CONCEPT: " << std::endl;
}
}
// After collecting all declarations, test a class against a concept
auto ConceptIter = ConceptMap.find("HasValueType"); // Replace with actual concept name
auto ClassIter = ClassMap.find("SimpleClass"); // Replace with actual class name
if (ConceptIter != ConceptMap.end() && ClassIter != ClassMap.end()) {
CheckConceptUsage(SemaRef, ConceptIter->second, ClassIter->second);
}
std::cout << "Testing all classes against all concepts.\n";
for (auto const &kv1 : ConceptMap) {
ConceptDecl const *concept = kv1.second;
std::cout << "- concept " << concept->getNameAsString() << ":\n";
for (auto const &kv2 : ClassMap) {
clang::CXXRecordDecl const *clazz = kv2.second;
std::cout << "-- class " << clazz->getNameAsString() << ":\n";
CheckConceptUsage(SemaRef, concept, clazz);
}
}
}
private:
ASTContext *Context;
CompilerInstance &CI;
void printDeclKind(Decl *D) {
std::cout << "Declaration Kind: " << D->getDeclKindName() << std::endl;
}
void printConceptDetails(const clang::ConceptDecl *CD, clang::ASTContext *Context) {
std::cout << "Concept: " << CD->getNameAsString() << std::endl;
ConceptMap[CD->getNameAsString()] = CD;
std::cout << std::endl;
}
void handleClass(CXXRecordDecl *CRD) {
if (!CRD->isThisDeclarationADefinition()) {
return; // Skip forward declarations
}
std::cout << "Class: " << CRD->getNameAsString() << std::endl;
// Store the class in the map
ClassMap[CRD->getNameAsString()] = CRD;
}
};
class FindFunctionsAction : public ASTFrontendAction {
public:
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef file) override {
return std::make_unique<FunctionConsumer>(CI);
}
};
int main(int argc, const char **argv) {
auto ExpectedParser = CommonOptionsParser::create(argc, argv, MyToolCategory);
if (!ExpectedParser) {
llvm::errs() << ExpectedParser.takeError();
return 1;
}
CommonOptionsParser& OptionsParser = ExpectedParser.get();
ClangTool Tool(OptionsParser.getCompilations(), OptionsParser.getSourcePathList());
// Manually add the required C++ standard flag
std::vector<std::string> CompileFlags = {"-std=c++20"};
Tool.appendArgumentsAdjuster(getInsertArgumentAdjuster(CompileFlags, ArgumentInsertPosition::BEGIN));
return Tool.run(newFrontendActionFactory<FindFunctionsAction>().get());
}
为了完整起见,我的所有测试都是使用 Clang 16.0.0 执行的。