Interoperation with any

OpenMethod can take any (both the std and boost flavors) as virtual arguments, and dispatch on the type of the contained value. For this purpose, it regards the types as "deriving" from any.

Requirements

Dispatch keys on the std::type_info returned by any::type(), so the registry’s rtti policy must be std_rtti, or a policy derived from it. default_registry and indirect_registry both qualify. A registry with, say, static_rtti identifies classes by a different kind of type_id, and would look up the wrong v-table; the requirement is enforced with a static_assert.

std::any

Support is provided by <boost/openmethod/interop/std_any.hpp>. It is not included by <boost/openmethod.hpp>, so it must be included explicitly.

Dispatch works on classes known to a registry. The types the any may contain are registered automatically: naming a type as the parameter of an overrider registers it as a class derived from std::any; storing a value in a virtual_std_any (see below) registers its type as well. A type that is never named in one of these ways is not registered and cannot be dispatched on - not even by a catch-all overrider: a call with such a value in the any is a missing_class error - see Error Handling.

The any is passed like any other virtual argument that is not a virtual_ptr: wrapped in virtual_, as described in Alternatives to virtual_ptr. Overriders receive the contained value by reference. An overrider may also take the any itself; such an overrider is a catch-all, applying to any registered contained type that has no more specific overrider - in the example below, float, which the weigh overrider registers, but which has no name overrider of its own:

#include <any>
#include <iostream>
#include <string>

#include <boost/openmethod.hpp>
#include <boost/openmethod/interop/std_any.hpp>

using namespace boost::openmethod;

struct Dog {
    std::string name;
};

// `std::any` is the common base of the types it may contain. An overrider
// registers the type it names as a class derived from it.
BOOST_OPENMETHOD(name, (virtual_<const std::any&>), std::string);

// An overrider takes the contained value...
BOOST_OPENMETHOD_OVERRIDE(name, (const Dog& dog), std::string) {
    return dog.name + " the dog";
}

BOOST_OPENMETHOD_OVERRIDE(name, (const std::string& name), std::string) {
    return name;
}

BOOST_OPENMETHOD_OVERRIDE(name, (const int& value), std::string) {
    return std::to_string(value) + " the integer";
}

// ...or the `any` itself, which makes it a catch-all.
BOOST_OPENMETHOD_OVERRIDE(name, (const std::any&), std::string) {
    return "something else";
}

BOOST_OPENMETHOD(weigh, (virtual_<const std::any&>), float);

BOOST_OPENMETHOD_OVERRIDE(weigh, (const float& value), float) {
    return value;
}

#include <boost/openmethod/initialize.hpp>

int main() {
    initialize();

    std::any spot = Dog{"Spot"};
    std::any felix = std::string("Felix the cat");
    std::any answer = 42;
    std::any pi = 3.14f;

    std::cout << name(spot) << "\n";   // Spot the dog
    std::cout << name(felix) << "\n";  // Felix the cat
    std::cout << name(answer) << "\n"; // 42 the integer

    // `float` is registered - `weigh`'s overrider names it - but has no
    // `name` overrider of its own, so the catch-all applies.
    std::cout << weigh(pi) << "\n";    // 3.14
    std::cout << name(pi) << "\n";     // something else
}

Mixing with ordinary virtual parameters

A multi-method can take any combination of ordinary virtual parameters and virtual any in the same call:

BOOST_OPENMETHOD(
    meet, (virtual_<const std::any&>, virtual_ptr<const Animal>), std::string);

BOOST_OPENMETHOD_OVERRIDE(
    meet, (const Dog& dog, virtual_ptr<const Cat>), std::string) {
    return dog.name + " meets a cat";
}

Reference categories

All three reference categories are supported, and they determine what the overriders may take:

Method parameter Overrider parameter

virtual_<const std::any&>

const Dog&, Dog

virtual_<std::any&>

Dog&, const Dog&, Dog

virtual_<std::any&&>

Dog&&, const Dog&, Dog

The mutable lvalue reference is the awkward one. BOOST_OPENMETHOD_OVERRIDE locates the method by checking that the overrider’s parameters can be passed to the method’s forwarder, and Dog& does not convert to std::any&. A temporary std::any binds to const std::any& and to std::any&&, which is why the other two categories can use the macro; nothing binds to a mutable lvalue reference. Those overriders are registered with the core API instead - the primitive the macro itself expands to:

using bump_method =
    BOOST_OPENMETHOD_TYPE(bump, (virtual_<std::any&>), std::string);

auto bump_dog(Dog& dog) -> std::string {
    dog.name += " Jr.";
    return dog.name;
}

BOOST_OPENMETHOD_REGISTER(bump_method::override<bump_dog>);

virtual_std_any

Every call above looks the v-table up in a hash table, keyed on the type the any contains. virtual_std_any - an alias for virtual_any<std::any> - removes that cost: it bundles an any with the v-table pointer for the value inside it, acquiring it once, on construction, and maintaining it across assignment and emplace. It is similar to virtual_ptr, except that it owns the object: the any is held by value.

The pointer comes from a lookup when the virtual_std_any is built from an existing any, and from a static variable - no lookup at all - when it is built from a value, or by emplace, since the type is then known at compile time. Building from a value, or emplace, also registers the type, like naming it in an overrider does; so does assigning a value.

That makes it worthwhile when the same value is dispatched on repeatedly. Its usefulness is limited, though, by the fact that the wrapper is not what an overrider receives: an overrider takes the contained value, as before, so it cannot pass the virtual_std_any on to another method and save the lookup there. Only a catch-all overrider, which takes const virtual_std_any&, gets it.

A virtual_std_any method parameter must be a reference - passing it by value would copy the any, and the value inside it, on every call. The three categories, and the limitation on the mutable one, are as above.

For the same reason that a virtual_std_any caches what a plain any does not, final_virtual_ptr is deleted for std::any: it would silently produce the v-table of the any root class rather than the one for the contained value.

A virtual_std_any cannot itself be wrapped in a virtual_ptr: it is already a wide type, and the result would be a wide pointer whose v-table is the contained value’s, but whose static type is the virtual_std_any - which is deliberately not a registered class. The combination is rejected at compile time, final_virtual_ptr included. Pass it by reference instead.

boost::any

boost::any is supported as well, by <boost/openmethod/interop/boost_any.hpp>, with virtual_boost_any - the exact counterpart of virtual_std_any. The two root classes are distinct, so std::any and boost::any may be used in the same program, and with the same registry.

virtual_any itself is generic: it can serve any type with an any-like interface, given virtual_traits specializations for its reference types. virtual_any registers the types it stores; a specialization that wants overriders to register the types they name, like the std::any and boost::any ones do, plants the registration in its cast and vptr functions. Boost.TypeErasure’s any is supported on the same model - see Interoperation with Boost.TypeErasure.

Acknowledgment

This interop is based on a design contributed by Steven Watanabe.