Disclaimer: this is an old, defunct project, filled with bugs and failed tests. It likely has issues out of the box on many platforms. It should only serve as a proof of concept/guide to how this functionality could be more rigorously implemented.
A C++ compiler with added meta-programming features:
- static reflection (of just about anything - templates, function definitions, other reflections, you name it),
- metaparsing (parse string literals as code, e.g. into individual template instantiations),
- custom compiler diagnostics (pointing to locations you define, with fixits you define, displayed by your IDE just like built-in diagnostics), and
- constexpr containers (vector, set, map -- or add your own!) of any of your constexpr classes.
Use these to define your own metaclasses, metanamespaces, meta-metafunctions, etc. -- recursion works endlessly.
This work is based around llvm/clang 7.0.0. More significantly, this
work is adapted from Andrew Sutton's original reflection/metaclasses repository:
https://github.com/asutton/clang.
Many thanks to him for sharing his excellent work; without it these additional
contributions would not have been possible.
Please also check out his newer work (https://gitlab.com/lock3/clang) for an alternate implementation along these lines.
template<typename T>
struct MyClassTmplA {
T t;
int i;
void f();
};
constexpr {
using namespace cppx::meta;
using namespace cppx::meta::clang;
auto_ CTD = cast<ClassTemplateDecl>(reflexpr(MyClassTmplA));
CTD->
// ^Your IDE will provide suggestions for all reflection properties
// (100s of them -- just about every public const method in the clang AST!)
// And if you make changes to clang, just recompile and the new properties
// are automatically reflected!
ce_assert(CTD->getNumTemplateParameters() == 1);
FOR ((Decl *) D : CTD->getTemplatedDecl()->decls()) {
if constexpr (D->isImplicit())
continue;
if constexpr (auto_ FD = dyn_cast<FieldDecl>(D)) {
auto_ QT = FD->getType();
if (QT->isDependentType())
ce_debug("Field named ", FD->getQualifiedNameAsString(), " has a dependent type");
} else {
D->dump(); //displays in your build-time output
ce_error( D->getBeginLoc() //your IDE will register an error pointing at D
, "Unhandled Decl kind; see dump"
, user::FixItHint::CreateRemoval(user::SourceRange(D->getBeginLoc(), D->getEndLoc()));
);
}
}
}
constexpr {
__queue_metaparse("static const int i = 3;");
__queue_metaparse("static const int j = i + ");
constexpr int jval = __metaparse_expr(__concatenate("3", 2+2, " + 5"), int);
__queue_metaparse(__concatenate(jval, ";"));
__queue_metaparse(__concatenate(
"constexpr { __queue_metaparse(__concatenate(\"static const int k = ",
1+1,
" + 2 + \", 4)); }"
));
//i; //ERROR: undeclared identifier
//j; //ERROR: undeclared identifier
ce_assert(jval == 39);
//k; //ERROR: undeclared identifier
} //...queued metaparses performed here...
//---The last one is first eval'd to:
// constexpr { __queue_metaparse(__concatenate("static const int k = 2 + 2 + ", 4)); }
//---Then that is parsed and itself eval'd, etc.;
static_assert(i == 3);
static_assert(j == 42);
//static_assert(jval == 39); //ERROR: undeclared identifier
static_assert(k == 8);
struct MyKeyClass {
int i;
constexpr MyKeyClass(int i = 0) : i(i) {}
// Need this for any container elem class (see tutorial):
constexpr operator const char *() {
return __concatenate("MyKeyClass(", i, ")");
}
};
constexpr {
auto mymap = ce::map<MyKeyClass, const char *>();
mymap.assign(MyKeyClass(1), "struct Z1 {};");
mymap.assign(MyKeyClass(3), "struct Z3 {};");
mymap.assign(MyKeyClass(5), "struct Z5 {};");
nce_assert(mymap.contains(MyKeyClass(3)));
nce_assert(ce_streq(mymap.at(MyKeyClass(3)), "struct Z3 {};"));
mymap.assign(MyKeyClass(3), "struct ZZ3 {};"); //reassignment
nce_assert(ce_streq(mymap.at(MyKeyClass(3)), "struct ZZ3 {};"));
for (auto kv : mymap) {
__queue_metaparse(kv.second);
}
}
void dummyfunc1() {
Z1 z1;
ZZ3 zz3;
Z5 z5;
}
-
Download CMake if necessary.
-
Create a folder to house the source, build, and examples. We'll call it clang-meta.
mkdir clang-meta
- Clone this repository into that folder:
git clone https://github.com/drec357/clang-meta.git clang-meta
-
The clang-meta folder should now have our
llvm/andexamples/folders in it. Check that anllvm/tools/clangfolder exists, and that inllvm/projects/are three subdirectories:compiler-rt,libcxx, andlibcxxabi. -
Now, make a
build/folder (make sure you have 30 GB or so of space for it), and navigate to it:
cd clang-meta
mkdir build
cd build
- Run CMake on the llvm sources:
cmake ../llvm
- Do the initial build (may take a few hours):
cmake --build .
(Don't sweat the various warnings. I'll try to address them soon. They don't matter.)
- Build the clang-wreflection target:
cmake --build . --target clang-wreflection
-
Navigate to
build/include, and make sure ac++directory has been created, as well as aclient_reflection_impl.hppfile. Make note of the full path to the latter. Also, inbuild/bin, there should beclangandclang++executables or shortcuts. Make note of the full path to those as well -- you will need them shortly to instruct your IDE to use them as your C/CXX compilers. -
Open
clang-meta/examples/include/client_reflection_impl.hppfile. It is intended to be a helper shortcut to your mainclient_reflection_impl.hpp. Right now it contains a relative path that is probably correct for now. But it would be best to change that to the absolute path to your/.../clang-meta/build/include/client_reflection_impl.hpp, in case you want to move examples/include around later. -
Open your IDE -- I highly recommend XCode for Mac users; its code completer works very well for our purposes, whereas e.g. CLion's does not. You may need to try a few out.
-
Import our "examples" folder into a new project, or set up a new one from scratch; all you need is to #include each of the example files (
0_reflection.hpp,1_metaparsing.hppetc.). -
Change the compilers/other IDE settings for this project:
- In XCode: go to Build Settings for the project, then:
- Search for "index", and set "Enable Index-While-Building Functionality" to No.
- Search for "dialect", and set your C++ dialect to either C++17 or GNU++17
- Click the "+" button, click Add User-Defined Setting, and assign
CXXto the full path to your new clang++ binary (e.g./.../clang-meta/build/bin/clang++) - Click the "+" again and assign
CCto e.g./.../clang-meta/build/bin/clang.
- In CMake-based IDEs like CLion, you must set
CMAKE_CXX_COMPILERandCMAKE_C_COMPILERappropriately, and the language dialect.
- Then, build the example project. I recommend going through the examples in order, there is a lot of new stuff here.
Please create an "Issue" if you follow these instructions but cannot build the example project!
cd /PATH/TO/YOUR/clang-meta
git pull https://github.com/drec357/clang-meta.git
cd build
cmake --build . --target clang-wreflection
There are many C++ reflection proposals floating around. I believe the way I've done it here is the best. I have simply automated generation of reflection properties from the public const methods/fields of clang AST nodes and helper types, creating a massive reflection library in a matter of minutes.
E.g., if clang::NamespaceDecl has an isAnonymousNamespace() public const method, so the user will be able to access a reflexpr(mynamespace)->isAnonymousNamespace() reflection.
More generally, my argument is that the C++ reflection standards folks should focus efforts on the naming and e.g. method interfaces of clang AST nodes, cleaning them up as much as possible, and defining the reflection standard as -- ahem -- a mirror image of that.
It would be difficult -- developers like their idiosyncrasies and would very reasonably resist. But if it were to be done, I see at least three benefits over the existing reflection proposals:
-
No parallel code to maintain long-term -- e.g. you don't need to keep up to date an
NamespaceDecl::isAnonymousNamespace()member AND some sort ofreflectIsAnonymousNamespace()function. -
Reflection implementers needn't answer to complaints about what properties of this or that decl are or are not reflectible. Any change a user wants, she or he can make by modifying the AST interface and rebuilding
clang-wreflection-- and indeed they should. Interested parties can have discussions about what they want reflected without feeling they are hostage to the reflection implementers, and the implementers can work without having to engage in the hundreds of arguments sure to be required to make such decisions. The two are decoupled. Everyone benefits. -
Common programmers naturally become familiar with clang syntax, thus more able to contribute to clang's maintenance and development.
The metaparsing feature will, I suspect, have some detractors. It feels a bit Python-y; a bit out of place in strict typing environment like C++. This is why, I imagine, other proposals lean toward using cleaner, more C++-like statements; consider Mr. Sutton and co's implementation:
namespace a {
int foo();
}
// ASUTTON'S "INJECTION":
namespace b {
consteval {
meta::info afoorefl = reflexpr(a::foo);
meta::make_constexpr(afoorefl);
-> a_foo_refl;
}
}
Compare that to metaparsing:
// DWR'S "METAPARSING":
template<typename T>
constexpr const char *funcSigAsStr(T funcrefl) {
/*...textual copying of function signature ...*/
}
namespace b {
constexpr {
auto_ afoorefl = reflexpr(a::foo);
QPARSE((afoorefl->isConstexpr() ? "" : "constexpr "), funcSigAsStr(afoorefl));
}
}
I prefer the latter for the same reasons as above: with the metaparsing solution, there is
- no parallel code to maintain a la
make_constexpr-- new parse-able keywords etc. are automatically supported; and - Implementers needn't answer to complaints about what is or is not "injectible", or what properties of a reflection are modifiable before injection.
But metaparsing's most obvious benefit: it is dirt-simple. Anybody can figure it out in five minutes. Anybody can see how a generative metaprogram works by just glancing at it. This leaves more brainpower for considering the actual logic of the metaprogram.
Mr. Sutton's infrastructure has clearly been laboriously thought out, but it gets fairly complex, once he introduces __fragments and requires statements to declare dependencies. I suggest you see other metaprogramming examples of Mr. Sutton's at https://gitlab.com/lock3/clang/wikis/Metaprogramming-Introductory-Tutorial, think how you might do them with metaparsing, and perhaps consider the converse, see which feels better. I'd be interested to know if anyone finds anything that can be done his infrastructure that cannot be done with metaparsing.
Only the llvm/tools/clang folder has been modified from the original llvm 7.0.0.
I have annotated with //DWR ADDN or //DWR MOD etc. everywhere I have added to or modified code from the original -- hopefully this will make it straightforward to port this into clang 9 or wherever it's at now.
I have done likewise -- //ASUTTON ADDN -- for wherever I have copied over or adapted code from Andrew Sutton's original repository, linked above (which is based around clang 5.x).
As mentioned previously Mr. Sutton and co. have made subsequent progress along their own track for reflection & "reification"; I encourage you to check out their newer work:
https://gitlab.com/lock3/clang.
Their code is very impressive, but owing to the divergence of our implementations, I have not borrowed anything additional from it excepting what was already in Mr. Sutton's original 5.x code (ConstexprDecl, CXXTupleExpansionStmt, CXXConcatenateExpr, the idea for reflection classes as template instantiations to solve constexpr issues, various structural ideas, etc. -- you'll see all of ASUTTON's great contributions all annotated in my code).
Note that you only need to rebuild the clang-wreflection target when you change clang/tools/reflection-src-generator/GenReflectionSrc.cpp or one of the include/clang/AST headers (i.e. the reflected interfaces). When just making ordinary adjustments to clang cpps, just rebuild the clang target -- much faster.
Dave