Skip to content

Cylix/Reflex

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

45 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Reflex

C++14 Reflection Library.

Requirements

  • C++14
  • Boost.PP includes

Compiling

The library is headers only, so there is nothing to build and to link with your program.

Instead, you just have to include <reflex/reflex> in your source files and everything will work as expected.

However, you may need to install the library includes somewhere in your computer: for this, we use cmake. Just follow these steps:

git clone https://github.com/Cylix/reflex.git
cd reflex
mkdir build
cd build
cmake -DCMAKE_INSTALL_PREFIX=/destination/path ..
make install

Examples and Tests

If you want to build the examples or the tests, follow these steps:

git clone https://github.com/Cylix/reflex.git
cd reflex
./install_deps.sh # necessary only for building tests
mkdir build
cd build
cmake .. -DBUILD_TESTING=true # tests
cmake .. -DBUILD_EXAMPLES=true # examples
cmake .. -DBUILD_TESTING=true -DBUILD_EXAMPLES=true # tests and examples
make -j

Register class

In order to enable reflection, we must explicitly "register" our class.

For this, reflex provides a simple macro: REGISTER_CLASS_FUNCTIONS(type, list of functions).

  • Type is the type on which we want to enable reflection
  • List of functions is the list of member functions (non-static and static member functions) we want to be able to call during reflection. These functions can have any signature (there is no restriction concerning the return value type, the number of parameters and the types of these parameters).

The list of functions must be formatted in a specific way: (fct1)(fct2)(fct3).... This format is the format used by Boost.PP (which is used in this library) and must be respected in order to compile.

For example, if we have the following class:

class SomeClass {
public:
    int fct(void) {
        std::cout << "fct()" << std::endl;
    }

    static void other_fct(const std::string&, float) {
        std::cout << "other_fct()" << std::endl;
    }
};

We just need to do REGISTER_CLASS_FUNCTIONS(SomeClass, (fct)(other_fct)) and this will register SomeClass and its two member functions for reflection.

For namespaced classes, REGISTER_CLASS_FUNCTIONS can take an extra parameter: REGISTER_CLASS_FUNCTIONS((some_namespace)(nested_namespace), SomeClass, (fct)(other_fct)). Namespaces use the Boost.PP syntax with parentheses. The macro must be called outside any namespaces in order to avoid conflicts.

Register C-Style functions

Reflection is not only limited to class member functions. It can be also used on C-style functions.

For this, reflex provides a simple macro: REGISTER_FUNCTIONS(list of functions).

  • List of functions is the list of functions we want to be able to call during reflection. These functions can have any signature (there is no restriction concerning the return value type, the number of parameters and the types of these parameters).

The list of functions must be formatted in a specific way: (fct1)(fct2)(fct3).... This format is the format used by Boost.PP (which is used in this library) and must be respected in order to compile.

For example, if we have the following class:

int fct(void) {
    std::cout << "fct()" << std::endl;
}

void other_fct(const std::string&, float) {
    std::cout << "other_fct()" << std::endl;
}
};

We just need to do REGISTER_FUNCTIONS((fct)(other_fct)) and this will register the two functions for reflection.

For namespaced functions, REGISTER_FUNCTIONS can take an extra parameter: REGISTER_FUNCTIONS((some_namespace)(nested_namespace), (fct)(other_fct)). Namespaces use the Boost.PP syntax with parentheses. The macro must be called outside any namespaces in order to avoid conflicts.

Making Reflection

Each time we register a type and its member functions, it stores this type into the reflection_manager singleton class.

This class is the class which does the reflection. By calling reflection_manager::invoke<RetVal, Params...>("class_name", "function_name", ...), this will call class_name::function_name (on a new object).

A facility function with a more elegant syntax is also provided: reflection_maker<RetVal(Params...)>::invoke("class_name", "function_name", ...). It provides the std::function template syntax which is more readable.

If we take the previous example, by calling reflex::reflection_maker<void(const std::string&, float)>::invoke("SomeClass", "other_fct", some_str, some_float);, we will invoke SomeClass::fct.

reflection_maker::invoke is overloaded for C-Style functions: reflection_maker<RetVal(Params...)>::invoke("function_name", ...).

Making Reflection with instance

By default, reflection for member function is done on a new object.

It is however possible to make reflection on a custom object instance.

reflex::reflection_maker<void(const std::string&, float)>::invoke(&some_obj, "SomeClass", "other_fct", some_str, some_float); will invoke SomeClass::other_fct on some_obj instance.

This feature is available for pointer, std::shared_ptr and std::unique_ptr of the object.

How does it work

REGISTER_CLASS_FUNCTIONS and REGISTER_FUNCTIONS macros are based on the REGISTER_REFLECTABLE macro The REGISTER_REFLECTABLE macro uses variadic macro parameters and works with Boost.PP in order to iterate through the list of functions.

This macro will create a static object of type reflectable<SomeClass>. For example REGISTER_CLASS_FUNCTIONS(SomeClass, (fct)(other_fct)) will generates the following code:

static reflectable<SomeClass> reflectable_SomeClass(
    "SomeClass",
    { "fct", &SomeClass::fct },
    { "other_fct", &SomeClass::other_fct }
);

Another example: REGISTER_FUNCTIONS((fct)(other_fct)) will generates the following code:

static reflectable<reflex::Void> reflectable_(
    "",
    { "fct", &fct },
    { "other_fct", &other_fct }
);

This generation is done at compile time but the registration is only effective at runtime at the beginning of the execution. Registering a class for reflection will impact on the compilation time and at program startup (when all static variables are initialized).

The constructor of a reflectable object registers itself into the reflectable_manager that we can use for reflection.

If you want to read more about the implementation of this library, you may be interested in an article I've written about it: Reflection in C++14.

Examples

Some examples are provided in this repositories:

To Do

Type Description Priority Status
Improvement Improve test coverage High To Do
Improvement Add full documentation Moderate To Do
Improvement Clear syntax for templates (<ReturnType, Params...> -> <ReturnType(Params)> whenever it is possible) Low To Do
Improvement Handling case of multiple REGISTER_FUNCTIONS (or multiple REGISTER_CLASS_FUNCTIONS for same type) in a single project Moderate To Do
Feature Reflection for variables Low To Do
Feature Reflection for overloads Medium To Do

Author

Simon Ninon