Boost logo

Boost :

From: Peter Turcan (peterturcan_at_[hidden])
Date: 2025-04-29 22:35:34


Dmitry - thanks for being the review manager for this library, and Jean-Louis
- thanks for submitting it!

I am the technical writer for the C++ Alliance, and have only reviewed the
documentation.

*Review of OpenMethod library documentation*

The documentation falls short in a number of key areas that I try to
address in this review. It is good that there is enough information to
provide detailed feedback. Currently though the documentation would get a
"D" - not a pass mark - a decent base but requires work.

INTRODUCTION

An introduction to a library should be suitable for developers who are not
familiar with the role of open methods. They should already be decent C++
developers. The goal of an introduction is to answer the question - "why
should I be interested in this?". I suggest something like this:

Open methods are a language mechanism that allows you to define new
behaviors (essentially, methods) for existing types *without *modifying
those types. C++ doesn't natively support open methods in the way that some
dynamic languages (like Common Lisp) do. Keys to the purpose of open
methods are the *Open/Closed Principle* (OCP) - where a software entity
(class, module, function, etc.) should be *open *for extension but *closed *for
modification - and the more complicated concept of *multiple dispatch*. In
*single** dispatch* method resolution is based on the runtime type of a
single object, usually the one the method is called on. With multiple
dispatch method resolution is based on the runtime types of two or more
arguments. C++ supports single dispatch via virtual functions, multiple
dispatch has to be simulated and is coded into this library.

The main advantage of open methods is that they help prevent bugs when
modifying stable code. For example, say you have a stable text processor.
When a new file format becomes popular (say, a variant of markdown), your
code can be extended to support the new format without modifying the
existing code. In simple terms, *open methods allow for safer scaling of
software*.

Another specific use is you can add behavior involving multiple types, for
example adding collision handling between type `A` and type `B` that is to
date unsupported in your code. Say you had a simulation of watercraft, but
had not supported hovercraft. You now add hovercraft, and need to add
collision detection, and the effects of the collision to both parties, but
would prefer to do this without modifying your existing codebase. This is
where multiple dispatch is useful - both as a concept and a feature.

This introduction describes two use-cases that are easy to understand and
can now be referenced later in the documentation as needed to clarify
details. Of course, there may be better use-cases than those I have
described here.

The current introduction is interesting, but perhaps should be under a
secondary heading "History of Open Methods" that follows the intro.

To be clear - does the library implement ALL the features of the N2216
paper - if not all, then list those implemented.

It is not a good idea to require a reader reference documentation outside
of the library for essential knowledge. The N2216 paper is long and
heavy-going, but as it's not in our control the link could break during the
lifetime of the library (say, 5 to 15 years). It is totally OK to provide
references, though expect some reference links to break as time
passes. *Required
information should be in the library doc*.

A kinda nit - this is funny "You wanted a banana but what you got was a
gorilla holding the banana and the entire jungle." - but - is it really
true? If you have a class Banana - how do you get the gorilla and jungle -
wouldn't it be the other way around (jungle inherits animals, trees, trees
inherit fruit etc)? However, if this is true, perhaps give an example to
show how it is a problem?

GETTING STARTED

Following the introduction, there should be a *Getting Started* section
which goes over 1) Requirements 2) Steps to install 3) Dependencies (Boost
libs, std libs, other libs, etc.) 4) hello world tutorial.

Getting up and running should come before Tutorials.

PERFORMANCE

Perhaps add a table of performance of some sort that readers can relate to
- say, comparing an open method to a class method with both doing the same
thing.

TUTORIALS

Great that there is a range of tutorials addressing the worth of open
methods.

However, they are not really tutorials unless they contain steps for the
reader to follow. Step 1 - copy and paste this example. Step 2 - run it and
examine the output, Step 3 - add this feature and run again. Step 4 -
notice how the output has changed...etc. etc.

It would be most useful if the tutorials related to the use-cases first
introduced, showing how the desired results are achieved.

It is not clear enough what the library does, versus what the sample source
code does.

Great tutorials have well commented code - ideally you can follow the code
flow by reading the comments.

TERMS and ACRONYMS

Acronyms are introduced that I am unaware of, without an initial
explanation. For example,

Unlike function declarations, which can occur multiple times in a TU, an
overrider declaration cannot.

What is a TU? Can this be defined in full on first use? Similar for ADL,
AST, CRTP etc. And for terms such as "guide function" - add a definition on
first use.

REFERENCE

If all the components of the library are listed here, great, a reference
should be *complete*.

I would really like to see the Reference further divided into sections
based on type:
* Classes
** fields
** methods
* Interfaces
* Structures
* Constants
* Macros
* Functions (if external to any class)

- Currently we have to examine each entry, or guess (I don't like guessing)
what each entry is.

The biggest missing component though is the "why" (the use-case) - the
*Description
*of each entry should say in what situation you would be interested in this
construct - or to put it another way - what problem does having this solve
- or help with ?

If references are arranged alphabetically within each section (Classes,
Macros etc.) - they are easy to locate.

ERRORS and EXCEPTIONS

It is not clear to me what errors or exceptions might be thrown by any of
the entries. All errors/exceptions thrown by the library code should be
listed under the entry that might fire them. For maximum usefulness,
include a table of errors and exceptions and* what you should do about it
if you get such an error* could be described too - say likely causes and
likely resolutions.

A complete structured Reference, with descriptions, return values, use
cases and errors would be great to see.

ACKNOWLEDGEMENTS, REFERENCES

The documentation could end with any acknowledgements (designers, testers,
motivators) and References such as N2216 and no doubt others.

*In Summary:*
I had to refer to too much information outside of the library doc to get a
basic understanding of the use-cases and problems that are being addressed
here. The documentation as it stands describes what the code does but
almost entirely from an inward looking perspective and almost never
addresses the "when I should use this" from a use-case - or more likely a
component of a use-case - perspective.

Keys to necessary improvements would be:
1. an introduction explaining the "why I should be interested" with some
compelling use-cases. The more relatable the use-cases, the more interest
and users you will get.
2. step by step tutorials showing how the concepts of open methods solve
problems
3. structured and complete reference

*Bonus Points*
1. As open methods would often be added to existing software, are there *Best
Practices* on how to design and implement code so that the addition of open
methods at a later date is as seamless as can be? If so, add a section
entitled Best Practices before the Reference. This would be a good place to
reference external books or papers or articles if they are useful.
2. Are there other Boost libraries that play well with OpenMethods - even
if you have limited experience of this a start would be helpful.

*Having said all this - the case for open methods is quite strong, from the
safer scalability of software perspective in particular, and I hope my 2c
is useful.*

*-* thanks
*Peter Turcan*

On Tue, Apr 29, 2025 at 12:49 PM Ruben Perez via Boost <
boost_at_[hidden]> wrote:

> On Tue, 29 Apr 2025 at 18:30, Ruben Perez <rubenperez038_at_[hidden]> wrote:
> >
> > On Sun, 27 Apr 2025 at 15:15, Дмитрий Архипов via Boost
> > <boost_at_[hidden]> wrote:
> > >
> > > Dear Boost community. The peer review of the proposed Boost.OpenMethod
> will
> > > start on 28th of April and continue until May 7th. OpenMethods
> implements open
> > > methods in C++. Those are "virtual functions" defined outside of
> classes. They
> > > allow avoiding god classes, and visitors and provide a solution to the
> > > Expression Problem, and the banana-gorilla-jungle problem. They also
> support
> > > multiple dispatch. This library implements most of Stroustrup's
> multimethods
> > > proposal, with some new features, like customization points and
> > > inter-operability with smart pointers. And despite all that
> open-method calls
> > > are fast - on par with native virtual functions.
> > > [...]
> > > * What is your evaluation of the implementation?
> >
> > I've seen that many of the macros use __COUNTER__ to generate unique
> > identifiers. If I'm reading this correctly, this includes
> > BOOST_OPENMETHOD_DECLARE_OVERRIDER and
> > BOOST_OPENMETHOD_INLINE_OVERRIDE, which are supposed to be safe to be
> > placed in headers. Is this correct?
> >
> > I've had bad experiences with macros using __COUNTER__ in headers in
> > the past. boost/asio/coroutine.hpp, which simulates coroutines using
> > switch/cases, uses __COUNTER__. Placing such constructs in headers can
> > inadvertently yield ODR violations. In my case, the ODR violation went
> > like this:
> >
> > // header1
> > void f1() {
> > BOOST_ASIO_CORO_REENTER(...) { ... } // Internally uses __COUNTER__
> > }
> >
> > // header2
> > void f2() {
> > BOOST_ASIO_CORO_REENTER(...) { ... } // Internally uses __COUNTER__
> > }
> >
> > // f1.cpp
> > #include "header1.hpp" // __COUNTER__ here is 0
> > #include "header2.hpp" // __COUNTER__ here is 1
> >
> > // f2.cpp
> > #include "header2.hpp" // __COUNTER__ here is 0
> > #include "header1.hpp" // __COUNTER__ here is 1
> >
> > This makes f1() and f2() have distinct bodies in f1.cpp and f2.cpp,
> > which is an ODR violation. It caused random test failures under MSVC
> > in Release mode only, and it took me forever to identify the root
> > cause of the issue.
> >
> > Are the macros I mention also vulnerable to this problem?
>
> Answering my own question: no, they are not because the generated
> symbols have always static linkage. If used in headers as shown above,
> several, potentially differently named symbols will be generated, but
> that's okay since re-registering stuff multiple times seems to be a
> no-op.
>
> >
> > Cheers,
> > Ruben.
>
> _______________________________________________
> Unsubscribe & other changes:
> http://lists.boost.org/mailman/listinfo.cgi/boost
>


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk