Mirror reflection library 0.5.13
|
Constructs like namespaces, types, typedefs, classes, class member variables, base classes, etc. need to be registered before they can be reflected or otherwise used. Wait, you have to register everyting manually? That sucks. To simplify the registration process and to make it more convenient, the Mirror library provides several preprocessor macros described in greater detail in the reference accessible through the links provided below.
One of the reasons for using macros for registering various things in Mirror is, that they hide rather complex definitions of specializations of various templates and other details and are much more readable and understandable. Another reason for using macros is, that mirror needs to "remember" the name of the namespace, type, class, etc. This is where the preprocessor's stringization operator is extremly useful as it removes the necessity to explicitly pass the name in a c-string literal.
As a general rule all the registering macros must be used between the MIRROR_REG_BEGIN
and MIRROR_REG_END
macros.
This section is divided in the following subsections:
One of the first and most basic things which needs to be registered with Mirror is a namespace. Most of the new projects, at least the medium size or large ones use namespaces to separate their code from the source code of other libraries to avoid potential identifier conflicts. Everything implemented by a library is thus placed in one or even several (possibly nested) namespaces.
The Mirror library provides two ways how to register namespaces. The first is more universal and allows to register namespaces nested in any depth, but is rather complicated. The second method allows to register namespaces defined directly in the global scope (first-level namespaces), those nested in namespaces defined in global scopes (second-level namespaces) and namespaces nested in the second type of namespaces (third-level namespaces) more easily.
The following code shows how to register these first, second and third level namespaces using the first, more universal, but more complicated way. The registering begins with the MIRROR_REG_NAMESPACE_BEGIN
macro and ends with the MIRROR_REG_NAMESPACE_END
macro. The individual namespaces are registered with the MIRROR_REG_NAMESPACE
macro. One important thing which should not be forgotten is to use the macro MIRROR_INIT_NAMESPACE_MEMBER_LIST
which initializes a global list associated with the namespace in question. This list will contain meta-objects reflecting all types, typedefs, classes, templates, global variables nested in the said namespace:
// these namespaces are going to be registered namespace first { namespace second { namespace third { } // end of namespace third } // end of namespace second } // end of namespace first // all registering must be done in the Mirror's namespace MIRROR_REG_BEGIN // begin the registering of namespaces // MIRROR_REG_NAMESPACE_BEGIN // // register the namespace defined in the global scope // MIRROR_REG_NAMESPACE(first) // -> namespace first { // now we can register namespaces nested in ::first // namespace first { // // register the second-level nested namespace // MIRROR_REG_NAMESPACE(second) // ---> namespace second { // register namespaces in ::first::second // namespace second{ // // register the third-level nested namespace // MIRROR_REG_NAMESPACE(third) // -----> namespace third { // } } // end of namespace second // } } // end of namespace first // } // finish the registering of namespaces // MIRROR_REG_NAMESPACE_END // // initialize the lists of namespace members // MIRROR_INIT_NAMESPACE_MEMBER_LIST(first) // MIRROR_INIT_NAMESPACE_MEMBER_LIST(first::second) // MIRROR_INIT_NAMESPACE_MEMBER_LIST(first::second::third) // MIRROR_REG_END
Fortunately namespaces nested deeper than the third level are far less common then the first, second and third-level namespaces and for these three very important special cases the registering process can be simplified by using the quick-registering macros. Registering of the first-level namespaces (those defined directly in the global scope) can be done by the MIRROR_QREG_GLOBAL_SCOPE_NAMESPACE
, second-level namespaces can be registered with the MIRROR_QREG_NESTED_NAMESPACE
macro and the third-level namespaces with the MIRROR_QREG_NESTED_NESTED_NAMESPACE
macro:
// these namespaces are going to be registered namespace first { namespace second { namespace third { } // end of namespace third } // end of namespace second } // end of namespace first // again all registering must be done in the Mirror's namespace MIRROR_REG_BEGIN // register the ::first namespace MIRROR_QREG_GLOBAL_SCOPE_NAMESPACE(first) // register the ::first::second namespace MIRROR_QREG_NESTED_NAMESPACE(first, second) // register the ::first::second::third namespace MIRROR_QREG_NESTED_NESTED_NAMESPACE(first, second, third) MIRROR_REG_END
It can be said that there are two (or three) differend kinds of types in C++. The first are the native or intrinsic types defined by the language standard like bool
, char
, wchar_t
, short
, long
, float
, double
, etc. The second are the elaborated types defined as struct
, class
, union
or even enum
which are fundamentaly (directly or indirectly), based on the native types. The third kind of types are typedef(s)
which are however second-class citizens when compared to intrinsic and elabored types. Typedefs are basically just symbolic names for types of the first two kinds, but they do not have their own identity in terms of function overloading or template specializations, unless they are specified as opaque typedefs as proposed for the upcomming C++1x standard.
The native types can be basically characterized in terms or their own identity; their names which are the "carriers" of this identity in the program source code and by their domain (i.e. the set of values which which can be represented by the type in question).
Elaborated (structured) types have other important components like the base_classes from which they inherit, their member_variables and member_functions, constructors and destructors. All of these have their own characteristics like access type, storage class, inheritance type, parameters, template_parameters etc.
To make it short, there are three important aspects of types; whether they are structured or not, whether they are or are not parametric (templated), and whether they are or are not nested.
Since the group of the native C++ types is limited and most of these types are pre-registered by Mirror, it would seem that it is generaly not necessary to register non-elaborated types.
There are however applications which are not interested in the structure of types, in the inheritance, members, constructors, etc. They just need to work with the type names and to examine how the types are nested in the namespaces and the meta-data describing their structure is just a burden. Such applications can greatly reduce the complexity of type registering and the footprint of reflection if classes are registered just as types.
To register only the basic type information like the scope and the name of a type the MIRROR_REG_GLOBAL_SCOPE_TYPE
and the MIRROR_REG_TYPE
macros can be used. The first macro registers the types from the global scope, the second registers nested types.
If you don't want to use the pre-registered native C++ types including the meta-data describing the constructors you could register (some of) the native types in the following way:
// registering must be done in the Mirror's namespace MIRROR_REG_BEGIN MIRROR_REG_GLOBAL_SCOPE_TYPE(bool) MIRROR_REG_GLOBAL_SCOPE_TYPE(wchar_t) MIRROR_REG_GLOBAL_SCOPE_TYPE(short) MIRROR_REG_GLOBAL_SCOPE_TYPE(int) MIRROR_REG_GLOBAL_SCOPE_TYPE(unsigned int) MIRROR_REG_GLOBAL_SCOPE_TYPE(float) MIRROR_REG_GLOBAL_SCOPE_TYPE(double) MIRROR_REG_END
The next source code fragment shows how to register some of the nested standard types:
// registering must be done in the Mirror's namespace MIRROR_REG_BEGIN MIRROR_REG_TYPE(std, string) MIRROR_REG_TYPE(std, wstring) MIRROR_REG_END
The following example shows how to register user-defined types:
struct foo { //... }; namespace bar { struct foo { // ... }; namespace baz { struct foobar { // ... }; } // namespace baz } // namespace bar // again registering must be done in the Mirror's namespace MIRROR_REG_BEGIN MIRROR_REG_GLOBAL_SCOPE_TYPE(foo) // --> struct foo { }; // MIRROR_QREG_GLOBAL_SCOPE_NAMESPACE(bar) // --> namespace bar { // MIRROR_REG_TYPE(bar, foo) // ----> struct [bar::] foo { }; // MIRROR_QREG_NESTED_NAMESPACE(bar, baz) // ----> namespace baz { // MIRROR_REG_TYPE(bar::baz, foobar) // ------> struct [bar::baz::] foobar { }; // } // namespace baz // } // namespace bar MIRROR_REG_END
Besides the basic identity of the type, elaborated types have other (rather complex) internal structures, like for example the aforementioned base classes, member variables, member functions and constructors. Because of this complexity, the regstering process for elaborated types can also be rather complex.
Registering of a single elaborated type begins with the MIRROR_REG_CLASS_BEGIN
or the MIRROR_REG_GLOBAL_SCOPE_CLASS_BEGIN
macros which allow to specify the kind of the elaborated type like class
or struct
, the scope and the base type name and ends with the MIRROR_REG_CLASS_END
macro.
The following example shows the registering of simple elaborated types (without any base_classes, members or constructors) both defined on the global scope or nested in namespaces:
struct foo { }; namespace bar { class foo { }; namespace baz { struct foobar { }; } // namespace baz } // namespace bar // do the registering MIRROR_REG_BEGIN MIRROR_REG_GLOBAL_SCOPE_CLASS_BEGIN(struct, foo) // --> struct foo { MIRROR_REG_CLASS_END // --> }; // MIRROR_QREG_GLOBAL_SCOPE_NAMESPACE(bar) // --> namespace bar { // MIRROR_REG_CLASS_BEGIN(class, bar, foo) // ----> class [bar::] foo { MIRROR_REG_CLASS_END // ----> }; // MIRROR_QREG_NESTED_NAMESPACE(bar, baz) // ----> namespace [bar::] baz { // MIRROR_REG_CLASS_BEGIN(struct, bar::baz, foobar) // ------> struct [bar::baz::] foobar { MIRROR_REG_CLASS_END // ------> }; // } // namespace baz // } // namespace bar MIRROR_REG_END
The registering of class inheritance with all aspects begins with the MIRROR_REG_BASE_CLASSES_BEGIN
and ends with the MIRROR_REG_BASE_CLASSES_END
macro. The inheritance of a concrete base class can be registered with the MIRROR_REG_BASE_CLASS
macro as shown on the following sample code:
namespace baz { class foo { }; class bar { }; struct foobar : virtual foo , protected bar { }; } // namespace baz // do the registering MIRROR_REG_BEGIN MIRROR_QREG_GLOBAL_SCOPE_NAMESPACE(baz) // --> namespace baz { // MIRROR_REG_CLASS_BEGIN(class, baz, foo) // ----> class [baz::] foo { MIRROR_REG_CLASS_END // ----> }; // MIRROR_REG_CLASS_BEGIN(class, baz, bar) // ----> class [baz::] bar { MIRROR_REG_CLASS_END // ----> }; // MIRROR_REG_CLASS_BEGIN(struct, baz, foobar) // ----> struct [baz::] foobar MIRROR_REG_BASE_CLASSES_BEGIN // -----> : MIRROR_REG_BASE_CLASS(virtual, _, baz::foo), // ----> virtual [default-public] [baz::]foo, MIRROR_REG_BASE_CLASS(_, protected, baz::bar) // ----> [default-nonvirtual] protected [baz::]bar MIRROR_REG_BASE_CLASSES_END // ----> { MIRROR_REG_CLASS_END // ----> }; // } // namespace baz MIRROR_REG_END
Member variables of a class are registered in a section started by the MIRROR_REG_CLASS_MEM_VARS_BEGIN
macro and finished with the MIRROR_REG_CLASS_MEM_VARS_END
macro. The individual member variables are registered either with the MIRROR_REG_CLASS_MEM_VAR_GET_SET
or the MIRROR_REG_CLASS_MEM_VAR
macro.
namespace foo { class bar { private: static int a; public: static int get_a(void) { return a; } static void set_a(int i) { a = i; } bool b; char c; static double d; // etc. }; } // namespace foo // do the registering MIRROR_REG_BEGIN MIRROR_REG_CLASS_BEGIN(class, foo, bar) // --> class [foo::] bar MIRROR_REG_CLASS_MEM_VARS_BEGIN // { MIRROR_REG_CLASS_MEM_VAR_GET_SET( // private, // --> private: static, int, a, // ----> static int a; // public: _.get_a(), // ----> static int get_a(void) { ... } _.set_a(_a), // ----> static void set_a(int) { ... } false, // // not nil false // // not nil-able ) // MIRROR_REG_CLASS_MEM_VAR(public, _, _, b) // --> public: [default-storage] [autodetected type] b; MIRROR_REG_CLASS_MEM_VAR(_, _, char, c) // --> [public-from-previous] [default-storage] char c; MIRROR_REG_CLASS_MEM_VAR(_, static, _, d) // --> [public-from-previous] static [autodetected] d; MIRROR_REG_CLASS_MEM_VARS_END // MIRROR_REG_CLASS_END // --> }; // MIRROR_REG_END
Another important feature of types or classes are constructors. The native types have only the intrinsic default and copy constructors, the elaborated types can also have user-defined constructors with or without parameters.
struct foo { foo(void); foo(const foo&); foo(int x); foo(bool b, int x, int y, int z, double w, const std::string& s); }; // do the registering MIRROR_REG_BEGIN MIRROR_REG_GLOBAL_SCOPE_CLASS_BEGIN(struct, foo) // -> struct foo // { MIRROR_REG_CONSTRUCTORS_BEGIN // MIRROR_REG_DEFAULT_CONSTRUCTOR(_) // ---> [default-for-struct-public] foo(void); MIRROR_REG_COPY_CONSTRUCTOR(public) // ---> public: foo(const foo&); // MIRROR_REG_CONSTRUCTOR_BEGIN(public, 1) // ---> public: foo( MIRROR_REG_CONSTRUCTOR_PARAM(int, x) // ------------- > int x MIRROR_REG_CONSTRUCTOR_END(1) // -----------> ); // MIRROR_REG_CONSTRUCTOR_BEGIN(_, 2) // ---> [public-for-struct-public] foo( MIRROR_REG_CONSTRUCTOR_PARAM(bool, b) // --------------> bool b, MIRROR_REG_CONSTRUCTOR_PARAM(int, x) // --------------> int x, MIRROR_REG_CONSTRUCTOR_PARAM(int, y) // --------------> int y, MIRROR_REG_CONSTRUCTOR_PARAM(int, z) // --------------> int z, MIRROR_REG_CONSTRUCTOR_PARAM(double, w) // --------------> double w, MIRROR_REG_CONSTRUCTOR_PARAM(std::string, s) // --------------> const std::string& s MIRROR_REG_CONSTRUCTOR_END(2) // ------------> ); MIRROR_REG_CONSTRUCTORS_END // MIRROR_REG_CLASS_END // -> }; MIRROR_REG_END
In the previous example the types of the constructor parameters were input manually. However if the constructor has arguments which correspond to the class' member variables, the types of the constructor parameters can be deduced from the types of the respective member variables as shown in the following example:
// define some classes first ... namespace test { // a simple 3d vector struct vector { double x,y,z; vector(double _x, double _y, double _z) : x(_x) , y(_y) , z(_z) { } vector(double _w) : x(_w) , y(_w) , z(_w) { } vector(void) : x(0.0) , y(0.0) , z(0.0) { } // ... other members ... }; // a triangle defined by three vectors (points) struct triangle { vector a, b, c; triangle(const vector& _a, const vector& _b, const vector& _c) : a(_a) , b(_b) , c(_c) { } triangle(void){ } }; // a tetrahedron composed of a triangle base and an apex point struct tetrahedron { triangle base; vector apex; tetrahedron(const triangle& _base, const vector& _apex) : base(_base) , apex(_apex) { } }; } // namespace test // now register those classes MIRROR_REG_BEGIN // register the ::test namespace MIRROR_QREG_GLOBAL_SCOPE_NAMESPACE(test) // --> namespace test { // // register the test::vector struct // MIRROR_REG_CLASS_BEGIN(struct, test, vector) // --> struct [test::] vector MIRROR_REG_CLASS_MEM_VARS_BEGIN // { MIRROR_REG_CLASS_MEM_VAR(_, _, _, x) // --> [public-from-struct] [default-storage] [autodetected double] x; MIRROR_REG_CLASS_MEM_VAR(_, _, _, y) // --> [public-from-struct] [default-storage] [autodetected double] y; MIRROR_REG_CLASS_MEM_VAR(_, _, _, z) // --> [public-from-struct] [default-storage] [autodetected double] z; MIRROR_REG_CLASS_MEM_VARS_END // // // register the first constructor // // detect the type of the arguments from the types // // of the member variables with the same names // MIRROR_REG_CONSTRUCTORS_BEGIN // MIRROR_REG_CONSTRUCTOR_BEGIN(public, xyz) // ----> public: vector( MIRROR_REG_CONSTRUCTOR_PARAM(_, x) // ------> [autodetected-from-the-x-member double] _x, MIRROR_REG_CONSTRUCTOR_PARAM(_, y) // ------> [autodetected-from-the-y-member double] _y, MIRROR_REG_CONSTRUCTOR_PARAM(_, z) // ------> [autodetected-from-the-z-member double] _z MIRROR_REG_CONSTRUCTOR_END(xyz) // ----> ); // // here the autodetection would not work because // // there is no member variable named w // MIRROR_REG_CONSTRUCTOR_BEGIN(public, w) // ----> public: vector( MIRROR_REG_CONSTRUCTOR_PARAM(double, w) // ------> double w MIRROR_REG_CONSTRUCTOR_END(w) // ----> ); // MIRROR_REG_DEFAULT_CONSTRUCTOR(_) // ----> public: vector(void); MIRROR_REG_COPY_CONSTRUCTOR(_) // ----> public: vector(const vector&); MIRROR_REG_CONSTRUCTORS_END // MIRROR_REG_CLASS_END // --> }; // // register the test::triangle struct // MIRROR_REG_CLASS_BEGIN(struct, test, triangle) // --> struct triangle MIRROR_REG_CLASS_MEM_VARS_BEGIN // { MIRROR_REG_CLASS_MEM_VAR(_, _, _, a) // ----> [vector] a; MIRROR_REG_CLASS_MEM_VAR(_, _, _, b) // ----> [vector] b; MIRROR_REG_CLASS_MEM_VAR(_, _, _, c) // ----> [vector] c; MIRROR_REG_CLASS_MEM_VARS_END // // MIRROR_REG_CONSTRUCTORS_BEGIN // MIRROR_REG_CONSTRUCTOR_BEGIN(public, abc) // ----> triangle( MIRROR_REG_CONSTRUCTOR_PARAM(_, a) // ------> [vector] a, MIRROR_REG_CONSTRUCTOR_PARAM(_, b) // ------> [vector] b, MIRROR_REG_CONSTRUCTOR_PARAM(_, c) // ------> [vector] c MIRROR_REG_CONSTRUCTOR_END(abc) // ----> ); // MIRROR_REG_DEFAULT_CONSTRUCTOR(_) // ----> triangle(void); MIRROR_REG_COPY_CONSTRUCTOR(_) // ----> triangle(const triangle&); MIRROR_REG_CONSTRUCTORS_END // MIRROR_REG_CLASS_END // --> }; // // register the test::tetrahedron struct // MIRROR_REG_CLASS_BEGIN(struct, test, tetrahedron) // --> struct tetrahedron MIRROR_REG_CLASS_MEM_VARS_BEGIN // --> { MIRROR_REG_CLASS_MEM_VAR(_, _, _, base) // ----> [triangle] base; MIRROR_REG_CLASS_MEM_VAR(_, _, _, apex) // ----> [vector] apex; MIRROR_REG_CLASS_MEM_VARS_END // // MIRROR_REG_CONSTRUCTORS_BEGIN // MIRROR_REG_CONSTRUCTOR_BEGIN(public, ba) // ----> tetrahedron( MIRROR_REG_CONSTRUCTOR_PARAM(_, base) // ------> [triangle] base, MIRROR_REG_CONSTRUCTOR_PARAM(_, apex) // ------> [vector] apex MIRROR_REG_CONSTRUCTOR_END(ba) // --> // MIRROR_REG_COPY_CONSTRUCTOR(_) // ----> tetrahedron(const tetrahedron&); MIRROR_REG_CONSTRUCTORS_END // MIRROR_REG_CLASS_END // --> }; // --> } // namespace test MIRROR_REG_END //
Yet another important part of a class are member functions - functions (and operators) declared inside of a class, allowing to manipulate the internal state of instances of that class or to interact with other instances. Registering of the member_functions of a class is done between the MIRROR_REG_MEM_FUNCTIONS_BEGIN
and the MIRROR_REG_MEM_FUNCTIONS_END
macros. The registering of a single individual member function is begins with the MIRROR_REG_MEM_FUNCTION_BEGIN
macro and ends with the MIRROR_REG_MEM_FUNCTION_END
macro. The argments of a member function are registered with the MIRROR_REG_MEM_FUNCTION_PARAM
macro.
class foo { private: // ... public: double f(void); int f(int a, int b, int c) const; double f(double x, float y); static void g(bool b); }; // do the registering MIRROR_REG_BEGIN MIRROR_REG_GLOBAL_SCOPE_CLASS_BEGIN(class, foo) // -> class foo // { MIRROR_REG_MEM_FUNCTIONS_BEGIN // // public: MIRROR_REG_MEM_FUNCTION_BEGIN(public, _, double, f, _) // ---> [public] [default-nonstatic] double f(void); MIRROR_REG_MEM_FUNCTION_END(f, _, _) // MIRROR_REG_MEM_FUNCTION_BEGIN(public, _, int, f, abc) // ---> [public] [default-nonstatic] int f( MIRROR_REG_MEM_FUNCTION_PARAM(int, a) // ------> int a, MIRROR_REG_MEM_FUNCTION_PARAM(int, b) // ------> int b, MIRROR_REG_MEM_FUNCTION_PARAM(int, c) // ------> int c MIRROR_REG_MEM_FUNCTION_END(f, abc, const) // ---> ) const; // MIRROR_REG_MEM_FUNCTION_BEGIN(public, _, double, f, xy) // ---> [public] [default-nonstatic] double f( MIRROR_REG_MEM_FUNCTION_PARAM(double, x) // ------> double x, MIRROR_REG_MEM_FUNCTION_PARAM(double, y) // ------> double y MIRROR_REG_MEM_FUNCTION_END(f, xy, _) // ---> ); // MIRROR_REG_MEM_FUNCTION_BEGIN(public, static, void, g, _) // ---> [public] static void g( MIRROR_REG_MEM_FUNCTION_PARAM(bool, b) // ------> bool b MIRROR_REG_MEM_FUNCTION_END(g, _, _) // ---> ); MIRROR_REG_MEM_FUNCTIONS_END // MIRROR_REG_CLASS_END // -> }; MIRROR_REG_END
Conversion operators can be used to convert instances of one type to other (unrelated) types. In addition to casts, such operators allow to convert instances to types which are not base classes of the original type of the converted instance. Basically conversion operators are special non-static class member functions without parameters.
Conversion operators can be registered with Mirror by the means of the MIRROR_REG_CLASS_CONVERSIONS
macro, inside of the class registering block enclosed by the MIRROR_REG_CLASS_BEGIN
and the MIRROR_REG_CLASS_END
macros.
namespace myproject { class Y; class Z; class X { public: operator Y(void) const; operator Z(void) const; }; } // namespace myproject // do the registering MIRROR_REG_BEGIN MIRROR_QREG_GLOBAL_SCOPE_NAMESPACE(myproject) // --> namespace myproject { // MIRROR_REG_CLASS_BEGIN(class, myproject, X) // --> class [myproject::] X // { MIRROR_REG_CLASS_CONVERSIONS(( // public: myproject::Y, // ----> operator [myproject::] Y(void) const myproject::Z // ----> operator [myproject::] Z(void) const )) // MIRROR_REG_CLASS_END // --> }; // // } // namespace myproject MIRROR_REG_END
Further examples of registering of elaborated types combining base classes, member variables and constructors can be found in the section Examples of registering.
As said before typedefs are a kind of second-class citizens among C++'s types because they are not different and distinguishable from the source types (as which they are defined) in function overloads and template instantiations nor by the typeid
operator. In spite of this, typedefined types are important because they can convey more specific semantics to the source type on the conceptual level, even if this is not recognized by the language itself.
For example we can define a new type called kilogram
as float
. For the C++ typesystem these two types are equal, but to the programmer the new type name indicates, that she/he should not assign values of length, temperature, speed, etc. because this type serves for storing weight or mass in some specific units of measurement. It is questionable whether this is a satisfactory solution, but this is one of the usages of typedefs.
Another important and very common application of typedefs is type switching. This means that a library or an application or even the language standard itself defines a typedef which is defined differently on different platforms, differently in debug and release builds, etc. In such cases it is useful if a reflection facility can "see" typedefs and distinguish between them and the source types.
This is why the Mirror library allows to register (and reflect) typedefined types with the MIRROR_REG_GLOBAL_SCOPE_TYPEDEF
and the MIRROR_REG_TYPEDEF
and to use the registered typedef with the MIRROR_TYPEDEF
macro as shown by the following example:
namespace myapp { // type switching #if USE_UTF8 typedef std::string mystring; #else typedef std::wstring mystring; #endif // typedefs for specifying more specific type semantics typedef mystring name; typedef float kilogram; typedef float centimeter; // class using the typedefs when defining the members struct person { name first_name; name middle_name; name family_name; kilogram weight; centimeter height; }; } // namespace myapp // do the registering MIRROR_REG_BEGIN MIRROR_QREG_GLOBAL_SCOPE_NAMESPACE(myapp) // -> namespace myapp { // MIRROR_REG_TYPEDEF(myapp, mystring); // -> typedef {_whatever_} mystring; MIRROR_REG_TYPEDEF(myapp, name); // -> typedef {mystring} [myapp::]name; MIRROR_REG_TYPEDEF(myapp, kilogram); // -> typedef {float} [myapp::]kilogram; MIRROR_REG_TYPEDEF(myapp, centimeter); // -> typedef {float} [myapp::]centimeter; // MIRROR_REG_CLASS_BEGIN(struct, myapp, person) // -> struct [myapp::] person MIRROR_REG_CLASS_MEM_VARS_BEGIN // { MIRROR_REG_CLASS_MEM_VAR(_, _, MIRROR_TYPEDEF(myapp, name), first_name) // ---> [myapp::]name first_name; MIRROR_REG_CLASS_MEM_VAR(_, _, MIRROR_TYPEDEF(myapp, name), middle_name) // ---> [myapp::]name middle_name; MIRROR_REG_CLASS_MEM_VAR(_, _, MIRROR_TYPEDEF(myapp, name), family_name) // ---> [myapp::]name family_name; MIRROR_REG_CLASS_MEM_VAR(_, _, MIRROR_TYPEDEF(myapp, kilogram), weight) // ---> [myapp::]kilogram weight; MIRROR_REG_CLASS_MEM_VAR(_, _, MIRROR_TYPEDEF(myapp, centimeter), height)// ---> [myapp::]centimeter height; MIRROR_REG_CLASS_MEM_VARS_END // MIRROR_REG_CLASS_END // -> }; // } // namespace myapp MIRROR_REG_END
Now the Mirror library knows that the type of the myapp::person::first_name
member variable is the typedef myapp::name
and it also knows that this type is in fact defined as std::string
or std::wstring
based on the preprocessor configuration.
using namespace mirror; // reflect the myapp::person class and get the meta-data // describing the 0th member variable typedef mp::at_c<member_variables< MIRRORED_CLASS(myapp::person) >, 0>::type meta_myapp_person_0; // get the full name of the member variable meta_myapp_person_0::full_name(); // returns "myapp::person::first_name" // get the full name of the type of this member variable meta_myapp_person_0::type::full_name(); // returns "myapp::name" // get the type of the 'source' type of this member variable meta_myapp_person_0::type::type::base_name(); // returns "string" or "wstring" // reflect the myapp::person class and get the meta-data // describing the 3rd member variable typedef mp::at_c<member_variables< MIRRORED_CLASS(myapp::person) >, 3>::type meta_myapp_person_3; // get the full name of the member variable meta_myapp_person_3::full_name(); // returns "myapp::person::weight" // get the full name of the type of this member variable meta_myapp_person_3::type::full_name(); // returns "myapp::kilogram" // get the type of the 'source' type of this member variable meta_myapp_person_3::type::type::full_name(); // returns "float"
Class templates are special elaborated (half-)types having parameters which allow additional configuration of the type which is the result of the instantiation of the template.
This means that one of the main difference between classes and class templates is the presence of the template_parameters. This is also reflected in the registering process. Class template registering begins with the MIRROR_REG_CLASS_TEMPLATE_BEGIN
and ends with the MIRROR_REG_CLASS_END
.
The following example shows the registering of a template class named 'P' similar to the std::pair template, having two template parameters called 'First' and 'Second' and two member variables called 'first' and 'second':
MIRROR_QREG_GLOBAL_SCOPE_NAMESPACE(test) // -> namespace test // { MIRROR_REG_CLASS_TEMPLATE_BEGIN( // ---> template (typename First, typename Second), // ---> <typename First, typename Second> struct, test, P, (First, Second) // ---> struct [test::] P <First, Second> ) // { MIRROR_REG_CLASS_MEM_VARS_BEGIN // MIRROR_REG_CLASS_MEM_VAR(_, _, _, first) // -----> First first; MIRROR_REG_CLASS_MEM_VAR(_, _, _, second) // -----> Second second; MIRROR_REG_CLASS_MEM_VARS_END // MIRROR_REG_CLASS_END // ---> }; // } // namespace test
From the text and the examples above it is clear that the registering of classes can be rather complicated and too expressive.
However, with the help of advanced preprocessor magic it is possible to simplify the registering in some cases as explained in the following sub-sections. To use these quick registering macros you need to include a separate header:
#include <mirror/utils/quick_reg.hpp>
Like the other registering expressions described above all the quick registering macros must also be used between the MIRROR_REG_BEGIN
and MIRROR_REG_END
macros.
POD (plain old data) classes without custom constructors can be quickly registered with the MIRROR_QREG_POD_CLASS
macro. The std::tm
structure
struct tm { int tm_sec; int tm_min; int tm_hour; int tm_mday; int tm_mon; int tm_year; int tm_wday; int tm_yday; int tm_isdst; };
could be for example registered as follows:
MIRROR_QREG_GLOBAL_SCOPE_NAMESPACE(std) // -> namespace std // MIRROR_QREG_POD_CLASS(struct, std, tm, // -> struct [std::]tm { (tm_sec) // ---> int tm_sec; (tm_min) // ---> int tm_min; (tm_hour) // ---> int tm_hour; (tm_mday) // ---> int tm_mday; (tm_mon) // ---> int tm_mon; (tm_year) // ---> int tm_year; (tm_wday) // ---> int tm_wday; (tm_yday) // ---> int tm_yday; (tm_isdst) // ---> int tm_isdst; ) // -> };
This macro registers the elaborated type with all the enumerated member variables and registers the default constructor, copy constructor and the POD-initializer constructor.
This section contains several more elaborated examples showing registering of various constructs.
This example shows the registering of a namespace called 'test' containing a typedef 'an_int_type' and a struct 'A' having a single public nonstatic member variable 'a' with the typedef-ined type 'an_int_type':
MIRROR_QREG_GLOBAL_SCOPE_NAMESPACE(test) // -> namespace test // { MIRROR_REG_TYPEDEF(test, an_int_type) // ---> typedef int an_int_type; // MIRROR_REG_CLASS_BEGIN(struct, test, A) // ---> struct [test ::] A // { MIRROR_REG_CLASS_MEM_VARS_BEGIN // MIRROR_REG_CLASS_MEM_VAR( // // member variable with typedefined type and _, _, // // [default-access], [default-storage-class] MIRROR_TYPEDEF(test, an_int_type), a // -----> test::an_int_type a; ) // MIRROR_REG_CLASS_MEM_VARS_END // MIRROR_REG_CLASS_END // ---> }; // } // namespace test
Similar to the previous example, here we register a class 'B' with a single public member variable 'b':
MIRROR_QREG_GLOBAL_SCOPE_NAMESPACE(test) // -> namespace test // { MIRROR_REG_CLASS_BEGIN(class, test, B) // ---> class [test ::] B // { MIRROR_REG_CLASS_MEM_VARS_BEGIN // public: // // member named b with public access type, // // default storage class and automatically // // detected type MIRROR_REG_CLASS_MEM_VAR(public, _, _, b) // ------> bool b; MIRROR_REG_CLASS_MEM_VARS_END // MIRROR_REG_CLASS_END // ---> }; // } // namespace test
The following example is more elaborate and uses the previous two examples. It shows the registering of base class inheritance, registering of member variables and constructors.
MIRROR_QREG_GLOBAL_SCOPE_NAMESPACE(test) // -> namespace test // { MIRROR_REG_CLASS_BEGIN(struct, test, C) // ---> struct C MIRROR_REG_BASE_CLASSES_BEGIN // ----> : MIRROR_REG_BASE_CLASS(virtual, _, test::A), // -----> virtual [default-public] test::A, MIRROR_REG_BASE_CLASS(virtual, _, test::B) // -----> virtual [default-public] test::B MIRROR_REG_BASE_CLASSES_END // { MIRROR_REG_CLASS_MEM_VARS_BEGIN // -----> // begin registering of member variables MIRROR_REG_CLASS_MEM_VAR(_, _, _, c) // -----> char c; MIRROR_REG_CLASS_MEM_VARS_END // -----> // end registering of member variables MIRROR_REG_CLASS_END // ---> }; // MIRROR_REG_CLASS_BEGIN(struct, test, D) // ---> struct D MIRROR_REG_BASE_CLASSES_BEGIN // ----> : MIRROR_REG_BASE_CLASS(virtual, _, test::A), // -----> virtual test::A, MIRROR_REG_BASE_CLASS(virtual, _, test::B) // -----> virtual test::B MIRROR_REG_BASE_CLASSES_END // { MIRROR_REG_CLASS_MEM_VARS_BEGIN // -----> // begin registering of member variables // // explicitly specified access type - public // // [default-storage-class] // // explicitly specified type - double MIRROR_REG_CLASS_MEM_VAR(public, _, double, d)// -----> double d; MIRROR_REG_CLASS_MEM_VARS_END // -----> // end registering of member variables MIRROR_REG_CLASS_END // ---> }; // MIRROR_REG_CLASS_BEGIN(struct, test, E) // ---> struct E MIRROR_REG_BASE_CLASSES_BEGIN // ----> : MIRROR_REG_BASE_CLASS(virtual, _, test::C), // -----> virtual test::C, MIRROR_REG_BASE_CLASS(virtual, _, test::D) // -----> virtual test::D MIRROR_REG_BASE_CLASSES_END // { MIRROR_REG_CLASS_MEM_VARS_BEGIN // // // explicitly specified access type - public // // explicitly specified storage class - static // // automatically detected type MIRROR_REG_CLASS_MEM_VAR(public, static, _, e)// -----> static double e; MIRROR_REG_CLASS_MEM_VARS_END // MIRROR_REG_CLASS_END // ---> }; // MIRROR_REG_CLASS_BEGIN(struct, test, F) // ---> struct [test::] F MIRROR_REG_BASE_CLASSES_BEGIN // ----> : MIRROR_REG_BASE_CLASS(_, _, test::E), // -----> [default-nonvirtual] [default-public] test:E, MIRROR_REG_BASE_CLASS(virtual, _, test::C), // -----> virtual [default-public] test::C, MIRROR_REG_BASE_CLASS(virtual, _, test::D) // -----> virtual [default-public] test::D MIRROR_REG_BASE_CLASSES_END // { MIRROR_REG_CLASS_MEM_VARS_BEGIN // MIRROR_REG_CLASS_MEM_VAR(_, _, _, f) // -----> float f; MIRROR_REG_CLASS_MEM_VARS_END // MIRROR_REG_CLASS_END // ---> }; // MIRROR_REG_CLASS_BEGIN(struct, test, G) // ---> struct G MIRROR_REG_CLASS_MEM_VARS_BEGIN // { MIRROR_REG_CLASS_MEM_VAR(_, _, _, g) // -----> short g; MIRROR_REG_CLASS_MEM_VARS_END // // MIRROR_REG_CONSTRUCTORS_BEGIN // // constructors // // constructor needs access type specifier // // and an identifier unique in the scope // // of its class. Identifiers 'default' and // // 'copy' are reserved. // // [public] default constructor MIRROR_REG_DEFAULT_CONSTRUCTOR(_) // -----> G(void); // // public copy constructor MIRROR_REG_COPY_CONSTRUCTOR(public) // -----> G(const G&); // // // [public] constructor identified by 'x' MIRROR_REG_CONSTRUCTOR_BEGIN(_, x) // -----> G( MIRROR_REG_CONSTRUCTOR_PARAM(int, x) // -------> int x MIRROR_REG_CONSTRUCTOR_END(x) // -----> ); // // // public constructor identified by 'abd' MIRROR_REG_CONSTRUCTOR_BEGIN(public, abd) // -----> G( MIRROR_REG_CONSTRUCTOR_PARAM(int, a) // -------> int a, MIRROR_REG_CONSTRUCTOR_PARAM(int, b) // -------> int b, MIRROR_REG_CONSTRUCTOR_PARAM(double, d) // -------> double d MIRROR_REG_CONSTRUCTOR_END(abd) // -----> ); MIRROR_REG_CONSTRUCTORS_END // MIRROR_REG_CLASS_END // ---> }; // } // namespace test