Mirror reflection library 0.5.13

Registering with Mirror

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:

Registering namespaces

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

Registering types

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:

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

Registering classes

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.

Basics

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

Base class inheritance

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

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

Constructors

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                                         //

Member functions

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

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.

Registering typedefs

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"

Registering class templates

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

Quick registering

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:

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.

Quick-registering POD classes

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.

Examples of registering

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

Copyright © 2006-2011 Matus Chochlik, University of Zilina, Zilina, Slovakia.
<matus.chochlik -at- fri.uniza.sk>
<chochlik -at -gmail.com>
Documentation generated on Fri Dec 16 2011 by Doxygen (version 1.7.3).
Important note: Although the 'boostified' version of Mirror uses the Boost C++ libraries Coding Guidelines and is implemented inside of the boost namespace, it IS NOT an officially reviewed and accepted Boost library. Mirror is being developed with the intention to be submitted for review for inclusion to the Boost C++ libraries.