У меня есть небольшой код, который анализирует объявления 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
Думаю, мой вопрос был достаточно ясен. Это не программа, это просто MRE, помогающий запустить метод, отлаживать нечего. Способны ли вы понять разницу между настоящей программой и MRE?
Вы уверены, что правильно интерпретируете возвращаемое значение CheckConstraintSatisfaction
? согласно документации, он «возвращает true, если произошла ошибка и удовлетворение не может быть проверено, в противном случае — false» — так что false
, вероятно, просто означает, что проверка ограничения прошла успешно без ошибок, и фактический результат (если ограничение удовлетворено или нет) равен в параметре ConstraintSatisfaction
out.
@Turtlefight, я уже пытался использовать if (Satisfaction.IsSatisfied) {... но это тоже не сработало.
Исходный код в суть очень близко к работе. Я смог заставить его работать на заданном пример и еще один простой, с некоторыми изменениями.
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
В случае
ТипТребования ,
в исходном коде была закомментированная попытка создать
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.
Ожидаете ли вы, что люди будут отлаживать вашу программу за вас?