Untitled :: Boost.OpenMethod

Typically, `BOOST_OPENMETHOD` is used in headers, while `BOOST_OPENMETHOD_CLASSES` and `BOOST_OPENMETHOD_OVERRIDE` are used in implementation files.

Let’s use a payroll application as an example. We have two roles: `Employee` and `Salesman`, and a `pay` method that computes the monthly pay of an employee. We want to override and call `pay` from multiple translation units, so we put it in a header:

    // roles.hpp

    #ifndef ROLES_HPP
    #define ROLES_HPP

    #include <boost/openmethod.hpp>

    struct Employee { virtual ~Employee() = default; };

    struct Salesman : Employee {
        double sales = 0.0;
    };

    BOOST_OPENMETHOD(pay, (boost::openmethod::virtual_ptr<const Employee>), double);

    #endif // ROLES_HPP

`BOOST_OPENMETHOD` defines an inline function. Thus it cannot be called more than once with the same arguments in the same translation unit. The include guard sees to this.

Let’s write the override for "just" employees - they get a fixed salary. `BOOST_OPENMETHOD_OVERRIDE` adds an overrider to the method. We don’t want to add the same overrider multiple times - that would create an ambiguity. Thus it goes in an implementation file:

    // employee.cpp

    #include "roles.hpp"
    #include <boost/openmethod.hpp>

    BOOST_OPENMETHOD_OVERRIDE(
        pay, (boost::openmethod::virtual_ptr<const Employee>), double) {
        return 5000.0;
    }

    BOOST_OPENMETHOD_CLASSES(Employee);

Salesmen get a salary like employees, plus a commission on their sales. The `next` function, available only in the body of an overrider, calls the next most specialized overrider. It is similar to `super` in other languages, except that it does not stand for an object, but for an overrider.

    // salesman.cpp

    #include "roles.hpp"
    #include <boost/openmethod.hpp>

    BOOST_OPENMETHOD_OVERRIDE(
        pay, (boost::openmethod::virtual_ptr<const Salesman> emp), double) {
        return next(emp) + emp->sales * 0.05; // base + commission
    }

    BOOST_OPENMETHOD_CLASSES(Employee, Salesman);

Paytime!

    // main.cpp

    #include "roles.hpp"
    #include <iostream>

    #include <boost/openmethod/initialize.hpp>

    int main() {
        boost::openmethod::initialize();

        Employee bill;
        Salesman bob; bob.sales = 100'000.0;

        std::cout << "pay bill: $" << pay(bill) << "\n"; // pay bill: $5000
        std::cout << "pay bob: $" << pay(bob) << "\n"; // pay bob: $10000
    }

In the previous example, we used `next` to call the super-overrider. We can also call an overrider directly. To do this, we can declare the overrider in the header, and define it in an implementation file:

    // roles.hpp

    #ifndef ROLES_HPP
    #define ROLES_HPP

    #include <boost/openmethod.hpp>

    struct Employee { virtual ~Employee() = default; };

    struct Salesman : Employee {
        double sales = 0.0;
    };

    BOOST_OPENMETHOD(pay, (boost::openmethod::virtual_ptr<const Employee>), double);

    BOOST_OPENMETHOD_DECLARE_OVERRIDER(
        pay, (boost::openmethod::virtual_ptr<const Employee>), double);

    #endif // ROLES_HPP

Unlike function declarations, BOOST_OPENMETHOD_DECLARE_OVERRIDER cannot appear multiple times in a translation unit with the same arguments. Also, it requires the method itself to be defined prior using this macro.

Overriders are placed in overrider containers. An overrider container is a class template named after the method, declared in the current namespace. It is specialized for each overrider signature. Macro BOOST_OPENMETHOD_OVERRIDER takes the same arguments `BOOST_OPENMETHOD_OVERRIDE`, and expands to the corresponding specialization of the overrider container. Containers have a static member function `fn` that contains the body of the overrider, provided by the user. We can call the overrider for `Employee` like so:

    BOOST_OPENMETHOD_OVERRIDE(
        pay, (boost::openmethod::virtual_ptr<const Salesman> emp), double) {
        return BOOST_OPENMETHOD_OVERRIDER(
                   pay, (boost::openmethod::virtual_ptr<const Employee> emp),
                   double)::fn(emp) +
            emp->sales * 0.05; // base + commission
    }

This is similar to a virtual function calling a base overrider. Virtual functions don’t have the equivalent of Smalltalk’s or Python’s `super`, but OpenMethod does, it’s `next`. It is almost always the right choice.

The exception is: when performance is critical, we may want to inline the call to the base overrider. BOOST_OPENMETHOD_INLINE_OVERRIDE defines the overrider as an inline function, and it can go in a header file:

    // roles.hpp

    #ifndef ROLES_HPP
    #define ROLES_HPP

    #include <boost/openmethod.hpp>

    struct Employee { virtual ~Employee() = default; };

    struct Salesman : Employee {
        double sales = 0.0;
    };

    BOOST_OPENMETHOD(pay, (boost::openmethod::virtual_ptr<const Employee>), double);

    BOOST_OPENMETHOD_INLINE_OVERRIDE(
        pay, (boost::openmethod::virtual_ptr<const Employee>), double) {
        return 5000.0;
    }

    #endif // ROLES_HPP

With inlining the overrider for `Salesman` compiles to (clang-20, x64-linux):

    movsd	xmm0, qword ptr [rsi + 8]
    mulsd	xmm0, qword ptr [rip + .LCPI1_0]
    addsd	xmm0, qword ptr [rip + .LCPI1_1]
    ret

Edit this Page