enumの値と識別子を出力したい場合に面倒になるのが、値から識別子へ文字列変換です。
- 識別子と、識別子の文字列リテラルの両方を書く必要がある(同じことを2回書く面倒)
- if文やcase文で同じ判定を存在する識別子分かかないといけない(似たことたくさん書く面倒)
- 上の2つを識別子が増えるたびに繰り返すことになる(面倒が続く)
CやC++のPreprocessorのマクロには引数を文字列リテラルに変換する機能があり、これを使うとコーディングが楽になります。今回はenum値の出力に応用していますが、コツをつかむといろいろ応用が効きます。
- マクロなのでCでもC++でも使える
- 識別子を1回書くだけでよい
- ifやcaseの繰り返しもマクロで置換する
- 識別子を増やしても軽微な修正で済む
ソースコード(マクロなし)
正攻法でちゃんと書いた例。記述量が多くなり、また、変更が入ったときにはメンテも大変そうです。
main.cxx
#include <clang/AST/DeclarationName.h>
//--------------------------------------------------
// print NameKind
// see DeclarationName.h
// enum clang::DeclarationName::NameKind
//--------------------------------------------------
static void printNameKind(const clang::DeclarationName::NameKind NK) {
switch (NK) {
case clang::DeclarationName::Identifier:
llvm::outs() << NK << "=clang::DeclarationName::NameKind::Identifier" << "\n";
break;
case clang::DeclarationName::ObjCZeroArgSelector:
llvm::outs() << NK << "=clang::DeclarationName::NameKind::ObjCZeroArgSelector" << "\n";
break;
case clang::DeclarationName::ObjCOneArgSelector:
llvm::outs() << NK << "=clang::DeclarationName::NameKind::ObjCOneArgSelector" << "\n";
break;
case clang::DeclarationName::ObjCMultiArgSelector:
llvm::outs() << NK << "=clang::DeclarationName::NameKind::ObjCMultiArgSelector" << "\n";
break;
:
このような記述が延々つづく。
default:
llvm::outs() << NK << "=UnknownKind";
}
}
:
ソースコード(マクロで簡略化)
マクロで2重に書かなければならない部分、繰り返しさなければならない部分を書かなくて良くなります。また、clangではTokenKinds.defファイルへキーワードを外だししていて#includeすることで利用できるようになっています。下のソースコード内のprintPPKeywordKind関数ではこの手法を併用することで更にメンテンス性を良くしています。
main.cxx
#include <clang/AST/DeclarationName.h>
#include <clang/Basic/TokenKinds.h>
//--------------------------------------------------
// print NameKind
// see DeclarationName.h
// enum clang::DeclarationName::NameKind
//--------------------------------------------------
static void printNameKind(const clang::DeclarationName::NameKind NK) {
switch (NK) {
#define pNKID(N) case clang::DeclarationName::N: llvm::outs() << NK << "=clang::DeclarationName::NameKind::"#N;llvm::outs() << "\n"; break
pNKID(Identifier);
pNKID(ObjCZeroArgSelector);
pNKID(ObjCOneArgSelector);
pNKID(ObjCMultiArgSelector);
pNKID(CXXConstructorName);
pNKID(CXXDestructorName);
pNKID(CXXConversionFunctionName);
pNKID(CXXDeductionGuideName);
pNKID(CXXOperatorName);
pNKID(CXXLiteralOperatorName);
pNKID(CXXUsingDirective);
#undef pNKID
default:
llvm::outs() << NK << "=UnknownKind";
}
}
//--------------------------------------------------
// print PPKeywordKind
// see TokenKind.h
// enum clang::tok::PPKeywordKind
//--------------------------------------------------
static void printPPKeywordKind(const clang::tok::PPKeywordKind KK) {
switch (KK) {
#define PPKEYWORD(X) case clang::tok::pp_##X: {llvm::outs() << KK << "=clang::tok::pp_"#X; llvm::outs() << "\n"; break;}
#include <clang/Basic/TokenKinds.def>
#undef PPKEYWORD
default:
llvm::outs() << KK << "=UnknownKind";
}
}
int main(int argc, char ** argv)
{
printNameKind(clang::DeclarationName::NameKind::Identifier);
printPPKeywordKind(clang::tok::PPKeywordKind::pp_if);
return 0;
}
Makefile
Makefile
TARG += print_enum
SRCS += main.cxx
LIBS += -lLLVM
LIBS += -lclangFrontend -lclangSerialization -lclangDriver
LIBS += -lclangTooling -lclangParse -lclangSema
LIBS += -lclangAnalysis -lclangRewriteFrontend -lclangRewrite
LIBS += -lclangEdit -lclangAST -lclangLex -lclangBasic -lclangASTMatchers
OPTS += -g
#OPTS += -static -static-libgcc -static-libstdc++
FLAGS += -std=c++14 -Wno-unused-parameter -fno-strict-aliasing -fsanitize=address -fno-omit-frame-pointer
INCS += -I/usr/lib/llvm-10/include
LIBDS += -L/usr/lib/llvm-10/lib
$(TARG): main.cxx
clang++ $(SRCS) $(OPTS) $(FLAGS) $(INCS) $(LIBDS) $(LIBS) -o $(TARG)
実行結果
$ ./print_enum
0=clang::DeclarationName::NameKind::Identifier
1=clang::tok::pp_if
環境
$ clang++ --version
clang version 10.0.0-4ubuntu1
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin