- Highlight and unique features
- Facts and features
- Basic information
- Quick start
- Related projects
- Example code
- Documentations
- Build the test code
- Known compiler related quirks in MSVC
- Motivations
metapp is a cross platform C++ runtime reflection library.
metapp is light weight, powerful, fast, unique, non-intrusive, no macros, and easy to use.
Even if you don't need reflection, you may use metapp::Variant
with any C++ types, and you can enjoy the large amount of
built-in meta types.
- Allow to retrieve any C++ type information at runtime, such as primary types, pointer, reference, function, template,
const-volatile qualifiers, and much more. Can you understand the type
char const *(*(* volatile * (&)[][8])())[]
easily? metapp can understand it, including every CV, pointer, array, function, etc, in no time! - Allow runtime generic programming. For example, we can access elements in a container without knowing whether
the container is
std::vector
orstd::deque
orstd::list
, and without knowing whether the value type isint
,std::string
,MyFancyClass
, or another container. - Very easy to reflect templates. For example, we only need to reflect
std::vector
orstd::list
once, then we can get meta information forstd::vector<int>
,std::vector<std::string>
, or evenstd::vector<std::list<std::vector<std::string> > >
. - Imitate C++ reference extensively for better performance. For example, when getting a property value,
or get an element value from a container, a
metapp::Variant
of reference to the element is returned when possible, the element value is referenced instead of copied, so the memory and performance cost is kept as minimum as possible. - Good performance. The performance is roughly similar to or better than Qt meta system. The execution speed can no way be close to native C++, but it's fast enough for reflection library.
-
Powerful
- Support any C++ type information, such as primary, pointer, reference, function, template, const-volatile qualifiers, and much more.
- Support runtime generic programming.
- Unreflected types can be inspected. Reflection only provides more information, but not a must.
- True runtime reflection. Accessing fields and properties, calling methods, are truly runtime behavior, no template parameters are needed. All parameters and return values are passed via metapp::Variant.
- Imitate C++ reference extensively for better performance.
- Good performance. The performance is roughly similar to or better than Qt meta system. Performance, binary size and compile time are continuously optimized.
- Automatically type conversion when getting/setting fields, invoking methods, etc.
- Support multiple inheritance and hierarchy.
- Support using in dynamic library (plugins).
- Minimum usage on global stateful static data, be loose couple with global state as much as possible.
- Extendable. The core library is very slim with very few functions, most functions are implemented by
meta interface
which is very powerful and extendable.
-
Flexible and easy to use
- Building meta data doesn't require preprocessor, macros, or any external tool. Only use native C++ code, no need macros.
- Very easy to reflect templates.
- Non-intrusive. You don't need to modify your code for reflection.
- Doesn't require C++ RTTI.
- Use exceptions by default, but exceptions can be disabled.
- No configuration, no policies, easy to use, while keeps powerful.
- Loose coupled design. For example, constructors and member functions can be used without coupling with the class information.
- You don't pay for what you don't use. If you don't build the meta data, no any memory overhead. If you don't use the meta data, no any performance overhead. If you build and use the meta data, you get trivial memory and performance overhead beside very powerful reflection system.
- Written in standard and portable C++, only require C++11, and support later C++ standard (such as std::any and std::variant are supported if the compiler supports C++17).
- Cross platforms, cross compilers.
- No external dependency.
- The API is designed carefully and thoughtfully, and try to be enjoyable to use.
-
Language features that can be reflected
- Const volatile qualifiers, include top level CV, and CV in pointer, array and member function.
- Pointer, reference.
- Classes and nested inner classes.
- Templates.
- Accessibles (global variable, member data, property with getter/setter, etc).
- Callables (global function, member function, constructor, std::function, etc).
- Overloaded function.
- Default arguments of functions.
- Functions with variadic parameters.
- Enumerators.
- Constants in any data type.
- Namespace simulation.
- Array, multi-dimensional array.
-
Built-in reflected meta types
- Primary types (void, bool, int, unsigned int, etc).
- STL strings (std::string, std::wstring).
- STL smart pointers (std::shared_ptr, std::unique_ptr, std::weak_ptr).
- STL containers (std::vector, std::list, std::map, etc).
- Callables (free function, member function, std::function, constructor, overloaded function, default arguments function, variadic function, etc).
- Many other meta types.
- Adding new meta types is easy.
Apache License, Version 2.0
The project is under working in progress.
The first stable release will be v1.0.0.
To put the library to first release, we need to,
- Add more tests.
- Finish the metapp based Lua bind project (under development).
- Complete the documentations.
Any significant features such as supporting operators reflection will be developed after v1.0.0 is released.
You are welcome to try the project and give feedback. Your participation will help to make the development faster and better.
https://github.com/wqking/metapp
metapp requires C++ compiler that supports C++11 standard.
The library is tested with MSVC 2022, 2019, MinGW (Msys) GCC 8.3 and 11.3.0, Clang (carried by MSVC).
In brief, MSVC, GCC, Clang that has well support for C++11, or released after 2019, should be able to compile the library.
metapp
There are various methods to use metapp
1, Include the source code in your project directly.
Clone the source code, then add the 'include' folder inside metapp to your project include directory,
then add the source files int the 'src' folder to your project, then you can use the library.
metapp
doesn't need any configuration.
2, Or install using CMake and use it in CMake
If you are going to use metapp in CMake managed project, you can install metapp then use it in CMake.
In metapp root folder, run the commands,
mkdir build
cd build
cmake ..
sudo make install
Then in the project CMakeLists.txt,
# the project target is mytest, just for example
add_executable(mytest test.cpp)
find_package(metapp)
if(metapp_FOUND)
target_link_libraries(mytest metapp::metapp)
else(metapp_FOUND)
message(FATAL_ERROR "metapp library is not found")
endif(metapp_FOUND)
Note: when using the method 2 with MinGW on Windows, by default CMake will install metapp in system folder which is not writable.
You should specify another folder to install.
To do so, replace cmake ..
with cmake .. -DCMAKE_INSTALL_PREFIX="YOUR_NEW_LIB_FOLDER"
.
jsonpp - a JSON parser and dumper based on metapp, developed by metapp developer (wqking).
Here are some simple code pieces. There are comprehensive tutorials in the documentations.
Header for Variant
#include "metapp/variant.h"
To use all declared meta types, include this header
#include "metapp/allmetatypes.h"
v contains int.
metapp::Variant v { 5 };
// Get the value
ASSERT(v.get<int>() == 5);
cast v to double
metapp::Variant casted = v.cast<double>();
ASSERT(casted.get<double>() == 5.0);
Now v contains char array.
v = "hello";
ASSERT(strcmp(v.get<char []>(), "hello") == 0);
Cast to std::string.
casted = v.cast<std::string>();
// Get as reference to avoid copy.
ASSERT(casted.get<const std::string &>() == "hello");
Let's inspect the type const std::map<const int, std::string> * volatile *
const metapp::MetaType * metaType = metapp::getMetaType<
const std::map<const int, std::string> * volatile *>();
ASSERT(metaType->isPointer()); // The type is pointer
// Second level pointer
const metapp::MetaType * secondLevelPointer = metaType->getUpType();
ASSERT(secondLevelPointer->isPointer());
ASSERT(secondLevelPointer->isVolatile()); //second level pointer is volatile
// The pointed type (const std::map<const int, std::string>).
const metapp::MetaType * pointed = secondLevelPointer->getUpType();
ASSERT(pointed->isConst());
ASSERT(pointed->getTypeKind() == metapp::tkStdMap);
// Key type
ASSERT(pointed->getUpType(0)->getTypeKind() == metapp::tkInt);
ASSERT(pointed->getUpType(0)->isConst()); //key is const
// Mapped type.
ASSERT(pointed->getUpType(1)->getTypeKind() == metapp::tkStdString);
Here is the class we are going to reflect for.
class MyPet
{
public:
MyPet() : name(), age() {}
MyPet(const std::string & name, const int age) : name(name), age(age) {}
int getAge() const { return age; }
void setAge(const int newAge) { age = newAge; }
std::string bark() const { return "Bow-wow, " + name; }
int calculate(const int a, const int b) const { return a + b; }
std::string name; // I don't like public field in non-POD, here is only for demo
private:
int age;
};
// We can use factory function as constructor.
MyPet * createMyPet(const std::string & name, const int birthYear, const int nowYear)
{
return new MyPet(name, nowYear - birthYear);
}
Now let's DeclareMetaType
for MyPet. We DeclareMetaType
for all kinds of types,
not only classes, but also enumerators, templates, etc.
template <>
struct metapp::DeclareMetaType<MyPet> : metapp::DeclareMetaTypeBase<MyPet>
{
// Reflect the class information via MetaClass.
static const metapp::MetaClass * getMetaClass() {
static const metapp::MetaClass metaClass(
metapp::getMetaType<MyPet>(),
[](metapp::MetaClass & mc) {
// Register constructors
mc.registerConstructor(metapp::Constructor<MyPet ()>());
mc.registerConstructor(metapp::Constructor<MyPet (const std::string &, int)>());
// Factory function as constructor
mc.registerConstructor(&createMyPet);
// Register field with getter/setter function
mc.registerAccessible("age",
metapp::createAccessor(&MyPet::getAge, &MyPet::setAge));
// Register another field
mc.registerAccessible("name", &MyPet::name);
// Register member functions
mc.registerCallable("bark", &MyPet::bark);
mc.registerCallable("calculate", &MyPet::calculate);
}
);
return &metaClass;
}
};
Now let's use the reflected meta class.
Obtain the meta type for MyPet, then get the meta class. If we've registered the meta type of MyPet
to MetaRepo, we can get it at runtime instead of depending on the compile time getMetaType
.
const metapp::MetaType * metaType = metapp::getMetaType<MyPet>();
const metapp::MetaClass * metaClass = metaType->getMetaClass();
getConstructor
, then invoke the constructor as if it's a normal callable, with proper arguments.
Then obtain the MyPet instance pointer from the returned Variant and store it in a std::shared_ptr
.
The constructor is an overloaded callable since there are three constructors registered,
metapp::callableInvoke
will choose the proper callable to invoke.
std::shared_ptr<MyPet> myPet(metapp::callableInvoke(metaClass->getConstructor(), nullptr,
"Lovely", 3).get<MyPet *>());
// Verify the object is constructed properly.
ASSERT(myPet->name == "Lovely");
ASSERT(myPet->getAge() == 3);
// Call the factory function, the result is same as myPet with name == "Lovely" and age == 3.
std::shared_ptr<MyPet> myPetFromFactory(metapp::callableInvoke(metaClass->getConstructor(), nullptr,
"Lovely", 2019, 2022).get<MyPet *>());
ASSERT(myPetFromFactory->name == "Lovely");
ASSERT(myPetFromFactory->getAge() == 3);
Get field by name then get the value.
const auto & propertyName = metaClass->getAccessible("name");
ASSERT(metapp::accessibleGet(propertyName, myPet).get<const std::string &>() == "Lovely");
const auto & propertyAge = metaClass->getAccessible("age");
ASSERT(metapp::accessibleGet(propertyAge, myPet).get<int>() == 3);
Set field name
with new value.
metapp::accessibleSet(propertyName, myPet, "Cute");
ASSERT(metapp::accessibleGet(propertyName, myPet).get<const std::string &>() == "Cute");
Get member function then invoke it.
const auto & methodBark = metaClass->getCallable("bark");
ASSERT(metapp::callableInvoke(methodBark, myPet).get<const std::string &>() == "Bow-wow, Cute");
const auto & methodCalculate = metaClass->getCallable("calculate");
// Pass arguments 2 and 3 to `calculate`, the result is 2+3=5.
ASSERT(metapp::callableInvoke(methodCalculate, myPet, 2, 3).get<int>() == 5);
Let's define a concat
function that processes any Variant that implements meta interface MetaIterable
std::string concat(const metapp::Variant & container)
{
// `container` may contains a pointer such as T *. We use `metapp::depointer` to convert it to equivalent
// non-pointer such as T &, that eases the algorithm because we don't care pointer any more.
const metapp::Variant nonPointer = metapp::depointer(container);
const metapp::MetaIterable * metaIterable
= metapp::getNonReferenceMetaType(nonPointer)->getMetaIterable();
if(metaIterable == nullptr) {
return "";
}
std::stringstream stream;
metaIterable->forEach(nonPointer, [&stream](const metapp::Variant & item) {
stream << item;
return true;
});
return stream.str();
}
A std::vector of int.
std::vector<int> container1 { 1, 5, 9, 6, 7 };
Construct a Variant with the vector. To avoid container1 being copied, we move the container1 into Variant.
metapp::Variant v1 = std::move(container1);
ASSERT(container1.empty()); // container1 was moved
Concat the items in the vector.
ASSERT(concat(v1) == "15967");
We can also use std::list. Any value can convert to Variant implicitly, so we can pass the container std::list on the fly.
ASSERT(concat(std::list<std::string>{ "Hello", "World", "Good" }) == "HelloWorldGood");
std::tuple is supported too, and we can use heterogeneous types.
ASSERT(concat(std::make_tuple("A", 1, "B", 2)) == "A1B2");
Isn't it cool we can use std::pair as a container?
ASSERT(concat(std::make_pair("Number", 1)) == "Number1");
We can even pass a pointer to container to concat
.
std::deque<int> container2 { 1, 2, 3 };
ASSERT(concat(&container2) == "123");
Declare a value to be referred to.
int n = 9;
rn holds a reference to n.
C++ equivalence is int & rn = n;
metapp::Variant rn = metapp::Variant::reference(n);
ASSERT(rn.get<int>() == 9);
Assign to rn with new value. C++ equivalence is rn = (int)38.1;
where rn is int &
.
Here we can't use rn = 38.1;
where rn is Variant
, that's different meaning.
See Variant document for details.
rn.assign(38.1); // different with rn = 38.1, `rn = 38.1` won't modify n
rn gets new value.
ASSERT(rn.get<int>() == 38);
n is modified too.
ASSERT(n == 38);
We can use reference to modify container elements as well.
vs holds a std::vector<std::string>
.
metapp::Variant vs(std::vector<std::string> { "Hello", "world" });
ASSERT(vs.get<const std::vector<std::string> &>()[0] == "Hello");
Get the first element. The element is returned as a reference.
metapp::Variant item = metapp::indexableGet(vs, 0);
assign to item with new value.
item.assign("Good");
ASSERT(vs.get<const std::vector<std::string> &>()[0] == "Good");
Assume we have two types that we don't reflect for.
struct UnreflectedFoo { int f; };
enum class UnreflectedBar { one, two };
Surely we can't get any member function or property information for the classes since they are not reflected.
But at least we can,
1, Construct the object using default constructor.
std::unique_ptr<UnreflectedFoo> foo(static_cast<UnreflectedFoo *>(
metapp::getMetaType<UnreflectedFoo>()->construct()));
ASSERT(foo->f == 0);
2, Copy construct the object.
foo->f = 38; // Change member value to prove the value is copied.
std::unique_ptr<UnreflectedFoo> copiedFoo(static_cast<UnreflectedFoo *>(
metapp::getMetaType<UnreflectedFoo>()->copyConstruct(foo.get())));
ASSERT(copiedFoo->f == 38);
3, Construct the object inplace.
UnreflectedFoo foo2 { 9 };
ASSERT(foo2.f == 9);
metapp::getMetaType<UnreflectedFoo>()->placementConstruct(&foo2);
ASSERT(foo2.f == 0);
4, Copy construct the object inplace.
foo2.f = 38;
UnreflectedFoo foo3 { 19 };
ASSERT(foo3.f == 19);
metapp::getMetaType<UnreflectedFoo>()->placementCopyConstruct(&foo3, &foo2);
ASSERT(foo3.f == 38);
5, Destroy the object, if we dont' use RAII
void * foo5 = metapp::getMetaType<UnreflectedFoo>()->construct();
// `destroy` knows the type of foo5 and can destroy the `void *` properly.
metapp::getMetaType<UnreflectedFoo>()->destroy(foo5);
6, Identify the meta type
const UnreflectedFoo * fooPtr = &foo3;
metapp::Variant varFoo = fooPtr;
metapp::Variant varBar = UnreflectedBar();
ASSERT(varBar.getMetaType()->equal(metapp::getMetaType<UnreflectedBar>()));
ASSERT(varFoo.getMetaType()->isPointer());
ASSERT(varFoo.getMetaType()->getUpType()->isConst());
ASSERT(varFoo.getMetaType()->equal(metapp::getMetaType<const UnreflectedFoo *>()));
ASSERT(varFoo.getMetaType()->getUpType()->equal(metapp::getMetaType<const UnreflectedFoo>()));
Below are tutorials and documents. Don't feel upset if you find issues or missing stuff in the documents, I'm not
native English speaker and it's not that exciting to write document. Any way, the code quality is always much better
than the document, for ever.
If you want to contribute to the documents, be sure to read How to generate documentations.
-
Tutorials
-
Core concepts and classes
-
Build and use meta data
-
Meta interfaces
-
Built-in meta types
-
Utilities
-
Miscellaneous
-
Tips and tricks
There are several parts of code to test the library,
- unittests: tests the library.
- docsrc: documentation source code, and sample code to demonstrate how to use the library.
- benchmark: measure the performance.
All parts are in the tests
folder.
All parts require CMake to build, and there is a makefile to ease the building.
Go to folder tests/build
, then run make
with different target.
make vc19
#generate solution files for Microsoft Visual Studio 2019, then open metapptest.sln in folder project_vc19make vc17
#generate solution files for Microsoft Visual Studio 2017, then open metapptest.sln in folder project_vc17make vc15
#generate solution files for Microsoft Visual Studio 2015, then open metapptest.sln in folder project_vc15make mingw
#build using MinGWmake linux
#build on Linuxmake mingw_coverage
#build using MinGW and generate code coverage report
MSVC 2022 and 2019, can build the CMake generated test projects and the tests run correctly in Debug and RelWithDebugInfo
configurations. But some tests fail in Release mode when incremental linking is disabled.
Those failed tests should not fail, because they work correct in MSVC debug mode and in GCC/Clang.
Adding /Debug option to linking which generates debug information makes the tests success.
Without /Debug option, but enabling incremental linking, will cause the tests success too.
So if metapp shows weird behavior in MSVC, try to enable incremental linking.
I (wqking) developed cpgf
library since more than 12 years ago. cpgf
works, but it has several serious problems.
The first problem is it was written in C++03, the code is verbose and difficult to write. The second problem is that
it includes too many features in a single library -- reflection, serialization, script binding (Lua, Python, JavaScript).
cpgf
became unmanageable and can't be developed in an elegant way.
metapp
is a fresh library that only focuses on reflection. Other features, such as serialization,
script binding, will be in separated projects, if they are developed.