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:
- a checker:
bool has_<OptionName>() constif the arity of the option is either?or* - a getter:
OptionType <OptionName>() const
The value of OptionType depends on the arity and the type of the option:
?or1:OptionTypeis eitherBaseTypeorBaseType const &*or+:OptionTypeisstd:vector<BaseType> constorstd::vector<BaseType::Ref> const &
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");