DirectivesΒΆ

The cpp-netlib uses a technique for allowing message-passing semantics in a chainable fashion in the form of directives. The basic concept for directives is, in a general sense, an encapsulated transformation that can be applied to objects that abide by the directive protocol.

Using the object-oriented notion of message passing, where an object accepts a message (usually a function call) we define a simple DSEL in order for the protocol to be supported by certain object types. In the cpp-netlib the protocol implemented is similar to that of the standard iostream formatting system:

object << directive1(...)
       << directive2(...)
       ...
       << directiveN(...);

In cpp-netlib the directives are simple function objects that take a target object as reference and returns a reference to the same object as a result. In code the directive pattern looks like the following:

struct directive_type {
    template <class Input>
    Input & operator()(Input & input) const {
        // do something to input
        return input;
    }
};

To simplify directive creation, usually factory or generator functions are defined to return concrete objects of the directive’s type.

inline
directive_type directive(...) {
    return directive_type();
}

The trivial implementation of the directive protocol then boils down to the specialization of the shift-left operator on the target type.

template <class Directive>
inline target_type & operator<<
(target_type & x, Directive const & f) {
    return f(x);
}

Todo

An example using a directive.

The rationale for implementing directives include the following:

  • Encapsulation - by moving logic into the directive types the target object’s interface can remain rudimentary and even hidden to the user’s immediate attention. Adding this layer of indirection also allows for changing the underlying implementations while maintaining the same syntactic and semantic properties.
  • Flexibility - by allowing the creation of directives that are independent from the target object’s type, generic operations can be applied based on the concept being modeled by the target type. The flexibility also afforded comes in the directive’s generator function, which can also generate different concrete directive specializations based on parameters to the function.
  • Extensibility - because the directives are independent of the target object’s type, new directives can be added and supported without having to change the target object at all.
  • Reuse - truly generic directives can then be used for a broad set of target object types that model the same concepts supported by the directive. Because the directives are self-contained objects, the state and other object references it keeps are only accessible to it and can be re-used in different contexts as well.

Extending a system that uses directives is trivial in header-only systems because new directives are simply additive. The protocol is simple and can be applied to a broad class of situations.

In a header-only library, the static nature of the wiring and chaining of the operations lends itself to compiler abuse. A deep enough nesting of the directives can lead to prolonged compilation times.