C++ code generation

ACE provides a Model-to-C++ compiler, called ace-compile, to enable tools to interact with configuration files. It also provides helper tools to instantiate the generated class based on configuration instances. If a configuration parsing class has been successfully instantiated, the tool developer has the guarantee that all accessed configuration option values are valid according to the defined model.

File organization

ace-compile generates three types of files: model class interfaces <ModelName>Instance.ac.h; model class declarations <ModelName>.ac.h; and model class definitions <ModelName>.ac.cpp. Model class interfaces define pure virtual interfaces that contain the accessor prototypes of a given model. Only interfaces are used within the definition of class type accessor prototypes.

Checkers and getters

For each option, the compiler generates two accessors:

The value of OptionType depends on the arity and the type of the option:

The value of BaseType depends on the kind of type involved. It may be either bool, int, float, std::string, or <ModelName>Interface. BaseType const & is used for std::string and <ModelOption>Interface types. std::vector<BaseType::Ref> const & is used for <ModelOption>Interface.

Handling inheritance

The inclusion of external models is handled using multiple inheritance of pure virtual interfaces. The final implementation of the derived models instantiates itself all the configuration values. As a consequence, the declaration and definition files of models that only serve as base model in a model definition are not generated. When to included models define the same class option, the option is merged in the final model if and only if either the class models are identical or if one is a direct descendent of the other. In the later case, the compiler leverages C++ co-variant return types to implement the option accessors.

The case of the Plugin type

The code generation of the plugin kind of type is handle a bit differently that the other types. The plugin C++ type generated is a map from std::string to MODEL::Ref objects, containing the instantiated plugins. MODEL::Ref is a reference to the instantiated object casted into its base type. It's up to the user to up-cast that reference to the expected type. Example:

"options": {
  "kind": "plugin", "arity": "1",
  "model": "Base.json"

The generated accessor:

std::map<std::string, Base::Ref> const & options() const;

For two triggered models ModelA and ModelB, the generic way to access the components is:

IModelA const & modelA = std::static_pointer_cast<IModelA const &>(options().at("a"));
IModelB const & modelB = std::static_pointer_cast<IModelB const &>(options().at("b"));

To avoid verbose casting, a helper templated function is also generated to access generic components. The helper's name is the name of the options suffixed with _at:

IModelA const & modelA = options_at<IModelA>("a");
IModelB const & modelB = options_at<IModelB>("b");