Common Preprocessor Macros#

Preprocessor macros are a C/C++ convention used to simplify code. Using #define we set some keyword to expand to a larger piece of code. These macros can also take arguments. For example:

#define HELLO std::cout << "Hello world!" << std::endl;
#define PRINT(...) std::cout << __VARGS__ << std::endl;

MasterType#

Defined in apps/integrated_abm/Master_Type.h MasterType is where we decide which template types are used throughout POLARIS, as well as store a few global variables like scenario and network. All Implementations in POLARIS have a MasterType template parameter, which allows them to refer back to this type and use all of the other classes throughout POLARIS.

Within the code, MasterType may be referred to simply as MT or tMT which expand to MasterType and typename MasterType respectively.

Global Variables#

A few variables within POLARIS are globally accessible but require MasterType to cast into a useable form. We use macros to simplify this and avoid retyping casts throughout the code:

#define global_scenario (reinterpret_cast<typename MasterType::scenario_type*>(_global_scenario))
#define global_network (reinterpret_cast<typename MasterType::network_type*>(_global_network))
#define global_demand (reinterpret_cast<typename MasterType::demand_type*>(_global_demand))
#define global_person_logger (reinterpret_cast<typename MasterType::person_data_logger_type*>(_global_person_logger))

Implementation Macros#

In general, within POLARIS, all our classes are sub-classed from one main parent class called Polaris_Component.

    template <typename MasterType      = NULLTYPE,
              typename InheritanceList = TypeList<>,
              typename ObjectType      = Data_Object>
    class Polaris_Component : public ObjectType

As can be seen, this is a templated class with three template parameters

  1. The MasterType from which all other type declarations can be obtained

  2. The inheritance tree of this class (as a list of types)

  3. The actual type of the object (Data or Execution)

When declaring such a sub-classes, they also need to be declared with appropriately named template types. These template types are used in the definition of the CRTP framework and have to be named and populated exactly.

  template <typename MasterType, typename InheritanceList = TypeList<>, typename DerivedType = void>
  struct MyClass
      : public Polaris_Component<MasterType, TypeList<MyClass>, Execution_Object>
  {...};

When defining methods a similar signature is required (but without the defaults)

  template <typename MasterType, typename InheritanceList, typename DerivedType>
  int MyClass<MasterType, InheritanceList, DerivedType>::_my_method()

To make this process easier and less error-prone, a number of helper macros are defined.

  // templateMID_d  - This is used when declaring any class or struct that inherits
  //                  from Polaris_Component (any where up the chain) and includes the appropritate default types
  template <tMT, typename InheritanceList = TypeList<>, typename DerivedType = void>

  // templateMID - This is used to add the correct template types when defining methods on such a class
  template <tMT, typename InheritanceList, typename DerivedType>

  // templateOnMID(ClassName) - This is fills in the actual full type of the sub-class including its templates.
  ClassName<MasterType, InheritanceList, DerivedType>

  // inheritListM(ClassName) - This is used to add the given class (templated on MT) to the inheritance list, useful
  //                           when declaring the inherited parent class
  typename TypeList_Append<InheritanceList, ClassName<MasterType>>::type

  // inheritComponent(ClassName, ObjectType) - This is used at the start of the inheritance chain to provide a Base_t
  //                                           that is an appropriately typed Polaris_Component
  Polaris_Component<MasterType, inheritListM(ClassName), ObjectType>

  // inheritFrom(Parent, Child) - This is used along the inheritance chain to provide a Base_t that is an appropriately
  //                              typed Parent<>
  Parent<MasterType, inheritListM(Child), templateOnMID(Child)>

These macros allow the above examples to be simplified greatly, the method definitions in particular to get much shorter.

  templateMID_d struct MyClass : public inheritComponent(MyClass, Execution_Object)
  {...};

  templateMID int templateOnMID(MyClass)::_my_method()

Member variable macros#

Classes and Structs in C++ have options for data and functions to be private, protected, or public. It is good practice to keep data protected and define public functions for accessing it, so that the data can be kept track of easily when debugging, so we define a few macros to make this easier.

t_data#

t_data is used for all basic and local data:

class Foo {
  t_data(int, my_variable);
}

This macro defines a protected int member variable called _my_variable and a getter (int my_variable()) and setter (void my_variable(int val)) method.

t_object#

t_data is most useful when the data being represented is sufficiently simple as defines a getter which returns by value. When we are storing more complex datatype this can be a significant performance penalty - t_object addresses this by defining a getter which returns by refenence .

class Foo {
  t_object(vector<int>, lots_of_ints);
}

Const-ness

Because the reference is not const - it can be used to modify the member variable directly

Debug Messages#

MasterType has a Logger class, explained in more depth HERE. For debugging, we usually use macros to simplify code writing and make these messages especially visible when reviewing errors that appear in POLARIS. For example:

DEBUG_WARNING("Variable should never exceed 1, is at " << variable);

Warning levels are explained in the Logging page.

There is also THROW_EXCEPTION which prints a message to the Logger at ERROR level, then throws and abort() call. This kills the function with a SIGABRT signal (the only time this should happen, if you see it in your stacktrace) so be careful using it.