OpenMethod catches most mistakes at compile time, and says what went wrong in a
static_assert. The rest surface at run time: initialize reports
inconsistencies in the registered classes, methods and overriders, and a
method call reports a combination of arguments it cannot dispatch.
This page walks through the mistakes that are made most often, then lists every diagnostic the library emits, with its cause and its remedy.
Compilation errors
OpenMethod is a library that emulates a language feature. A mistake therefore
surfaces as a failed template instantiation, and the compiler reports it
together with the whole instantiation context, much of it inside the library.
Where the library can tell what went wrong, it says so in a static_assert.
Search the output for the message:
| Compiler | Diagnostic |
|---|---|
gcc |
|
clang |
|
MSVC |
|
Read the first one. The errors that follow it are usually its consequences.
The overrider matches no method
#include <iostream>
#include <boost/openmethod.hpp>
using boost::openmethod::virtual_ptr;
class Node {
public:
virtual ~Node() = default;
};
class Plus : Node {};
BOOST_OPENMETHOD(postfix, (virtual_ptr<Node>, std::ostream&), void);
BOOST_OPENMETHOD_OVERRIDE(
postfix, (virtual_ptr<Plus>, std::ostream& os), void) {
os << "+";
}
static assertion failed: BOOST_OPENMETHOD_OVERRIDE: cannot find 'postfix' method that accepts the same arguments as the overrider
BOOST_OPENMETHOD_OVERRIDE does
not take the method as an argument: it finds the method by overload resolution,
looking for a method of the same name that can be called with the overrider’s
arguments. Here Plus derives from Node privately - class makes the
inheritance private unless told otherwise - so a virtual_ptr<Plus> does not
convert to a virtual_ptr<Node>, and postfix cannot be called with one. The
fix is class Plus : public Node.
The other causes of the same message are:
-
The method is not declared, or not before the overrider.
-
The name is misspelled.
-
The class of a virtual parameter does not derive from the class in the same position in the method, or does so ambiguously.
-
The overrider has more, or fewer, parameters than the method.
-
A virtual parameter is a reference in the method and a pointer in the overrider, or the other way around.
-
The method takes a
virtual_and the overrider avirtual_ptr. The overrider takes what the method takes, without thevirtual_:Plus&forvirtual_<Node&>. -
The method takes a smart
virtual_ptr, such asshared_virtual_ptr, and the overrider a plainvirtual_ptr, or one over another kind of smart pointer. A plainvirtual_ptrin the method and a smart one in the overrider has a message of its own:different virtual_ptr<> pointer kinds. -
A virtual parameter of the overrider is
constwhere the method’s is not. The opposite mistake has a message of its own: see The overrider discardsconst. -
A non-virtual parameter differs from the method’s.
-
The method is in another namespace, and its arguments do not make it visible through argument-dependent lookup. See Namespaces.
When the overrider and the method differ only by their registry, the message
says so instead: registry mismatch.
The overrider discards const
#include <iostream>
#include <boost/openmethod.hpp>
using boost::openmethod::virtual_ptr;
struct Node {
virtual ~Node() = default;
};
struct Plus : Node {};
BOOST_OPENMETHOD(postfix, (virtual_ptr<const Node>, std::ostream&), void);
BOOST_OPENMETHOD_OVERRIDE(
postfix, (virtual_ptr<Plus>, std::ostream& os), void) {
os << "+";
}
static assertion failed: overrider parameter discards const qualifier
The method takes a virtual_ptr<const Node>: it promises not to modify the
node. The overrider for Plus takes a virtual_ptr<Plus>, through which it
could. When the method calls the overrider, it casts its argument to the type
of the overrider’s parameter, and a cast cannot remove const. The fix is
virtual_ptr<const Plus>.
The same applies to references and pointers: an overrider for
virtual_<const Node&> takes a const Plus&. Clang follows the message with
the error from the cast itself, which names the two classes:
dynamic_cast from 'const Node' to 'Plus &' casts away qualifiers.
The class is not polymorphic
#include <iostream>
#include <boost/openmethod.hpp>
#include <boost/openmethod/initialize.hpp>
using boost::openmethod::virtual_ptr;
struct Node {};
struct Plus : Node {};
BOOST_OPENMETHOD(postfix, (virtual_ptr<Node>, std::ostream&), void);
BOOST_OPENMETHOD_OVERRIDE(
postfix, (virtual_ptr<Plus>, std::ostream& os), void) {
os << "+";
}
int main() {
boost::openmethod::initialize();
Plus plus;
postfix(plus, std::cout);
}
| Compiler | Diagnostic |
|---|---|
gcc |
|
clang |
|
MSVC |
|
The method and the overrider compile; the call does not. An object passed as a
virtual argument is converted to a virtual_ptr, which needs the dynamic
type of the object. The default registry finds it with typeid, which yields
the dynamic type only for a polymorphic class, so virtual_ptr does not accept
Node or classes derived from it, and the call matches no overload. There is
no static_assert here, because the mistake is not in a declaration that the
library sees: it is the absence of a conversion.
The usual fix is to make the root of the hierarchy polymorphic by giving it a
virtual destructor - which it needs anyway, if objects are ever deleted through
a pointer to it. Alternatively, inplace_vptr_base gives a class a v-table
pointer of its own, and a custom RTTI policy can find the dynamic type of
non-polymorphic classes: see Custom RTTI.
Where the caller knows the exact class of the object, final_virtual_ptr
makes a virtual_ptr without looking for the dynamic type. It takes the static
type of its argument instead, so it works with any registered class,
polymorphic or not:
Plus plus;
postfix(final_virtual_ptr(plus), std::cout);
Nothing checks that the static type is the dynamic type: with a non-polymorphic
class, OpenMethod has no way to find out, even with runtime checks enabled.
Passed a Node& that refers to a Plus, final_virtual_ptr makes a
virtual_ptr to a Node, and the call dispatches on Node.
The same mistake made with virtual_ instead of virtual_ptr, as in
BOOST_OPENMETHOD(postfix, (virtual_<Node&>, std::ostream&), void), is caught
when the method is declared, and reported with a message of its own:
virtual_<> parameter is not a polymorphic class.
Runtime errors
Errors detected at run time are passed to the registry’s
error_handler policy. The one in default_registry writes a short
description of the error to stderr, then the program is terminated with
abort. Error Handling explains how to change that,
for example to throw an exception.
initialize always checks the classes, methods and overriders it is
given, and a call that finds no overrider, or several ambiguous ones, is always
reported. The other checks made during a call - for an unregistered class, a
call before initialize, a misused final_virtual_ptr - are made only if
the registry contains the runtime_checks policy. default_registry
contains it if
BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS
is defined, which is recommended in debug builds. The symbol changes the
definition of default_registry, so it must be defined in all the translation
units of the program, or in none.
Missing class registrations
Every class that appears in a virtual parameter, in a method or in an
overrider, must be registered with
BOOST_OPENMETHOD_CLASSES. So must
the class of every object passed as a virtual argument. initialize
detects the first two omissions:
BOOST_OPENMETHOD_CLASSES(Dog); // Animal is missing
BOOST_OPENMETHOD(poke, (virtual_ptr<Animal>), void);
BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr<Dog>), void) {
// ...
}
// aborts with error message: unknown class Animal
initialize();
BOOST_OPENMETHOD_CLASSES(Animal); // Dog is missing
BOOST_OPENMETHOD(poke, (virtual_ptr<Animal>), void);
BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr<Dog>), void) {
// ...
}
// aborts with error message: unknown class Dog
initialize();
The third one can be detected only during the call, and only if runtime checks are enabled. Otherwise, the call reads a v-table pointer that was never set up, and the behavior is undefined: it may call the wrong overrider, or crash.
// Bulldog is missing
BOOST_OPENMETHOD_CLASSES(Animal, Dog);
BOOST_OPENMETHOD(poke, (virtual_ptr<Animal>), void);
BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr<Dog>), void) {
// ...
}
Bulldog hector;
// aborts with error message: unknown class Bulldog
poke(hector);
Missing base classes
initialize learns about inheritance relationships from the class
registrations: each lists a class together with its direct bases. Two classes
that are registered separately are unrelated, as far as OpenMethod is
concerned. If an overrider implies that one derives from the other,
initialize reports it:
// registered separately, so the inheritance is never seen
BOOST_OPENMETHOD_CLASSES(Animal);
BOOST_OPENMETHOD_CLASSES(Dog);
BOOST_OPENMETHOD(poke, (virtual_ptr<Animal>), void);
BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr<Dog>), void) {
// ...
}
// aborts with error message: missing base Animal -<| Dog
initialize();
The message names the base class first, then the derived class. The fix is to register the classes together:
BOOST_OPENMETHOD_CLASSES(Animal, Dog);
Calls that cannot be dispatched
A call reports not implemented if no overrider is applicable to the dynamic
types of its virtual arguments, and ambiguous if several are, and none of
them is more specialized than all the others. See
Multiple Dispatch.
Neither is a mistake by itself: a method may well be meant for some
combinations of classes only. However, initialize already knows about
every such combination, and says how many there are in its report. A program
can check it, and set the BOOST_OPENMETHOD_TRACE environment variable to
1 to find out which methods and classes are involved:
auto report = bom::initialize(bom::trace::from_env()).report;
if (report.not_implemented != 0 || report.ambiguous) {
std::cerr << "some methods are ambiguous or not implemented for "
"some combinations of virtual arguments\n"
"set BOOST_OPENMETHOD_TRACE=1 to troubleshoot\n";
exit(1);
}
The trace lists the classes, the methods, their overriders and the dispatch
tables that initialize builds from them. Its format is not specified, and
may change from one version to the next.
Message index
Each table quotes enough of a message to search for. Where a diagnostic is not
a static_assert, the wording depends on the compiler, and the table quotes
gcc.
Methods and overriders
| Message | Cause and remedy |
|---|---|
|
See The overrider matches no method above. |
|
The overrider was matched to a method, but one of its parameters is not
acceptable. It is accompanied by a more specific message, such as
|
|
A method needs at least one parameter declared with |
|
See The class is not polymorphic above. |
|
A |
|
An overrider takes the plain type: |
|
Where the method takes a |
|
A |
|
See The overrider discards |
|
The overrider returns the method’s return type, or a type that converts to
it, such as |
|
A |
|
Follows one of the two messages above, from the code that calls the overrider. Fix the first one. |
|
The non-virtual parameters of the overrider must have the same types as the method’s. |
|
The same mistake as in The overrider matches no method, detected by the core API rather than by the macro. |
Classes
| Message | Cause and remedy |
|---|---|
|
A class inherits the same base more than once, without virtual inheritance. OpenMethod cannot convert a pointer to the derived class to a pointer to that base. |
|
A class is registered where it is only declared. Register it after its definition. |
|
A class list names its registry last:
|
Virtual pointers and smart pointers
| Message | Cause and remedy |
|---|---|
|
The class is not polymorphic, so |
|
The object carries its own v-table pointer, for example because it derives
from |
|
A base class passed to |
|
Only the root of an |
|
|
|
A virtual parameter is declared as |
|
Pass a |
|
The method takes a |
|
Use |
|
A method cannot dispatch on an object that may no longer exist. Lock the pointer, and pass the resulting shared pointer. |
Registries
See Registry Affinity for how a class, a parameter and a method come to belong to a registry.
| Message | Cause and remedy |
|---|---|
|
A |
|
A virtual parameter names a registry, or its class declares an affinity for one, and the method names another. |
|
A class list names a registry, and one of the classes declares an affinity for another. |
|
The virtual parameters of a method that names no registry belong to different registries. Name the registry on the method, or make the parameters agree. |
|
A class list mixes classes that declare different affinities, or a class that declares one with a class that declares none. Add the registry at the end of the list. |
|
The class inherits different affinities from several bases, or inherits one from a private base. Declare its affinity in the class itself. |
|
The affinity declaration names a type that is not a registry. |
|
A |
any and type erasure
| Message | Cause and remedy |
|---|---|
|
Dispatching on the content of an |
|
A |
|
The same, for a Boost.TypeErasure |
|
A |
|
|
|
The overrider takes the content of the |
Policies
| Message | Cause and remedy |
|---|---|
|
The |
|
The state is restored from a destructor, while an exception is in flight, where throwing would terminate the program. |
|
Each stateful policy must define its own |
|
|
|
Compile every translation unit with |
|
The platform is not supported. |
C++26 reflection
| Message | Cause and remedy |
|---|---|
|
The groups passed to |
|
Put the namespaces, the classes and the registries in groups of their own. |
|
A group contains a reflection of something else, such as a function. |
|
A class listed explicitly is only declared, not defined. |
Runtime messages
Each message is written by the default error handler. The second column names the error type that a custom handler receives.
| Message | Error | Cause and remedy |
|---|---|---|
|
||
|
See Missing base classes. |
|
|
||
|
||
|
A method was called before |
|
|
|
|
|
|
|
|
|
|
|
|
The same, for |
|
|
The same, for |
|
|
Increase the limit, or use another |