[proposal] OpenMethod library

Hello Boost developers! I would like to propose my open-method library for inclusion in Boost. Open-methods are an elegant solution to the Expression Problem:
Given a set of types, and a set of operations on these types, is it possible to add new operations on the existing types, and new types to the existing operations, without modifying existing code?
Open-methods solve the Expression Problem by making it possible to define free virtual functions, i.e. virtual functions that exist outside of a class. They also help reduce coupling. Open-methods are often called "multi-methods", because they typically make it possible to select an overrider on the basis of more than one argument (as this library does). Nice, but this is a distraction. Observations show that single dispatch largely outnumbers multiple dispatch in real systems, even in languages that natively support multi-methods (Common Lisp, Clojure, Julia, Dylan, TADS, Cecil, Diesel, Nice, etc). "Multi" is just the cherry on the "open" cake. Bjarne Stroustrup wanted open-methods in C++ almost from the beginning. In D&E he writes:
I repeatedly considered a mechanism for a virtual function call based on more than one object, often called multi-methods. [...] Since about 1985, I have always felt some twinge of regret for not providing multi-methods. Stroustrup, 1994, The Design and Evolution of C++, 13.8.
Circa 2007, he tried to bring open-methods into the standard, unfortunately without success, to his enduring regret. Decades later he writes:
In retrospect, I don’t think that the object-oriented notation (e.g., x.f(y)) should ever have been introduced. The traditional mathematical notation f(x,y) is sufficient. As a side benefit, the mathematical notation would naturally have given us multi-methods, thereby saving us from the visitor pattern workaround. Stroustrup, 2020, Thriving in a Crowded and Changing World: C++ 2006–2020.
This is what this library is all about: f(x, y), and getting rid of visitors. It closely follows the design described in the papers by Stroustrup and his PhD students Peter Pirkelbauer and Yuriy Solodkyy. Method dispatch is *fast*. It uses v-tables similar to native virtual functions, and it can match their performance. The library also provides additional features: a mechanism to call the next most specialized overrider, support for smart pointers, customization points for alternative RTTI systems, error handling, tracing, etc. The library requires C++17 or above. It supports virtual and multiple inheritance, excluding repeated inheritance. For evaluation purposes, I prepared a repository with a library adapted to Boost naming conventions: https://github.com/jll63/Boost.OpenMethod. The documentation is here: https://jll63.github.io/Boost.OpenMethod. Here are a few examples from the tutorial on Compiler Explorer: * Hello World: https://godbolt.org/z/9hv8oE8zd * AST - combining virtual_ptr and std::unique_ptr: https://godbolt.org/z/cPjzfanc8 * Intrusive vptr: https://godbolt.org/z/87h9zv7dK * Policy for throwing exceptions: https://godbolt.org/z/nEYWGsPc5 If you like what you see, please consider endorsing the library. Thanks, Jean-Louis Leroy

El 08/03/2025 a las 21:37, Jean-Louis Leroy via Boost escribió:
Hello Boost developers!
I would like to propose my open-method library for inclusion in Boost.
[...]
If you like what you see, please consider endorsing the library.
Hi, I like and am endorsing this library. I think the design space it explores is interesting (and non trivial), and would like Jean-Louis's proposal to go through a formal review process. Joaquin M Lopez Munoz

On Sat, 8 Mar 2025 at 21:37, Jean-Louis Leroy via Boost < boost@lists.boost.org> wrote:
Hello Boost developers!
I would like to propose my open-method library for inclusion in Boost.
Out of curiosity, were you at using std::cpp last year? I recall a talk on this topic. Thanks, Ruben.

Out of curiosity, were you at using std::cpp last year? I recall a talk on this topic.
Yes that was me, and the proposed library is derived from YOMM2. Beyond the renaming, there are quite a few improvements: much less reliance on macro magic, more internals made public, improved virtual_ptr , which gets the spotlight, away from virtual_. J-L

I endorse this proposal. I also volunteer to manage the library's review.
Great! Gladly accepted. Also, thanks to Joaquin for endorsing. J-L On Mon, Mar 10, 2025 at 5:56 AM Дмитрий Архипов via Boost < boost@lists.boost.org> wrote:
сб, 8 мар. 2025 г. в 23:37, Jean-Louis Leroy via Boost < boost@lists.boost.org>:
I would like to propose my open-method library for inclusion in Boost.
I endorse this proposal. I also volunteer to manage the library's review.
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

On Sat, Mar 8, 2025 at 9:37 PM Jean-Louis Leroy via Boost
For evaluation purposes, I prepared a repository with a library adapted to Boost naming conventions: https://github.com/jll63/Boost.OpenMethod. The documentation is here: https://jll63.github.io/Boost.OpenMethod.
Nice doc, thanks. Quite interesting. One thing I asked myself reading the doc, was the handling of const-ness in method signatures, whether it was supported or not, and if it was, consequences on overloading. Another question I had relates to Steven's Boost.TypeErasure, and similarities / differences with your approach. Naively I'd think there was some overlap, at least for single-dispatch, no? Thanks, --DD [1]: https://www.boost.org/doc/libs/1_79_0/doc/html/boost_typeerasure.html

One thing I asked myself reading the doc, was the handling of const-ness in method signatures, whether it was supported or not, and if it was, consequences on overloading.
`const` is supported. Some examples use it (e.g. https://github.com/jll63/Boost.OpenMethod/blob/master/examples/matrix.cpp ).
Another question I had relates to Steven's Boost.TypeErasure, and similarities / differences with your approach. Naively I'd think there was some overlap, at least for single-dispatch, no?
First a disclaimer: I am not very familiar with Boost.TypeErasure. If I make incorrect claims about it, I apologize in advance, and please do correct me.
The two libraries are obviously in the same space: runtime polymorphism, and a solution to the Expression Problem (thanks to BOOST_TYPE_ERASURE_FREE). There are some major differences though, and I suspect that they can be counted in favor, or against, either library, depending on one's distate for inheritance. I will go over them starting with the more important then down.
As far as I can tell, TypeErasure, Rust traits and Go interfaces don't support open recursion. You are granted one jump through an `any`, and you land in an overrider with a plain reference. You lose polymorphism the first time through the door.
Related to this, objects returned from overriders have no means of carrying the functionality that may have initially existed in the any/trait/interface. Let's look at an example.
#include <iostream> #include < https://jll63.github.io/Boost.OpenMethod/boost/openmethod.hpp>
struct Matrix { virtual ~Matrix() = default; }; struct OrdinaryMatrix : Matrix {}; struct SymmetricMatrix : Matrix {};
using namespace boost::openmethod;
BOOST_OPENMETHOD_CLASSES(Matrix, OrdinaryMatrix, SymmetricMatrix);
BOOST_OPENMETHOD( transpose, (shared_virtual_ptr<Matrix>), shared_virtual_ptr<Matrix>);
BOOST_OPENMETHOD_OVERRIDE( transpose, (shared_virtual_ptr<OrdinaryMatrix>), shared_virtual_ptr<Matrix>) { return make_shared_virtual<OrdinaryMatrix>(); }
BOOST_OPENMETHOD_OVERRIDE( transpose, (shared_virtual_ptr<SymmetricMatrix> m), shared_virtual_ptr<Matrix>) { return m; }
BOOST_OPENMETHOD( to_json, (shared_virtual_ptr<Matrix>), std::string);
BOOST_OPENMETHOD_OVERRIDE( to_json, (shared_virtual_ptr<OrdinaryMatrix> m), std::string) { return "JSON for ordinary matrix"; }
BOOST_OPENMETHOD_OVERRIDE( to_json, (shared_virtual_ptr<SymmetricMatrix> m), std::string) { return "JSON for a symmetric matrix\n"; }
int main() { initialize();
auto m = make_shared_virtual<SymmetricMatrix>(); auto t = transpose(m); std::cout << to_json(t) << "\n";
return 0; }
(https://godbolt.org/z/xG91rfqGr)
Transposition, addition, etc are clearly part of a matrix API, whereas `to_json` is a cross-cutting concern. With open-methods, we can _add_ the "jsonable" functionality to _Matrix_ - even though we don't touch the source.
I guess that a translation of this example to `any` (or Rust) would look like this: have a MatrixType concept that states that matrices can be transposed (and added, etc). We can also add a concept, say, JsonSerializable stating that a Matrix can be converted to a JSON string. Can we make `to_json(transpose(m))` work? Without making MatrixType aware of JsonSerializable?
So this is the major differences, but there are a few others...
OpenMethod embraces inheritance. Some will dislike that. Live and let live...
It looks like creating an `any` always requires a call to `new`. Storage is managed via a `std::shared_ptr`. OpenMethod can work with zero allocations after `initialize` has been called. Later I will introduce local sequential allocators for completely alloc-free operation.
While the syntax of TypeErasure is really nice, I suspect that `any` declarations can become unwieldy in larger programs, in presence of a couple dozens of functions in the interface.
Oh yes, I almost forgot: multiple dispatch indeed :)
J-L

On Mon, Mar 10, 2025 at 9:45 PM Jean-Louis Leroy via Boost
[...] handling of const-ness `const` is supported.
Doesn't show the overloading part, but that's OK. Quick question: Given the ability to return different (necessarily related?) types from the same open-method, can we say BOM allows co-variant return type? Like virtual methods / polymorphism allows, just more flexible that plain pointers? Thanks, --DD

Doesn't show the overloading part, but that's OK.
Quick question: Given the ability to return different (necessarily related?) types from the same open-method, can we say BOM allows co-variant return type? Like virtual methods / polymorphism allows, just more flexible that plain pointers?
Yes. And in that case, the return type can act as a tie-breaker, as described in
N2216.
Here is `transpose` with `const` and covariant return types:
BOOST_OPENMETHOD(
transpose, (shared_virtual_ptr<const Matrix>),
shared_virtual_ptr<const Matrix>);
BOOST_OPENMETHOD_OVERRIDE(
transpose, (shared_virtual_ptr<const OrdinaryMatrix>),
shared_virtual_ptr<const OrdinaryMatrix>) {
return make_shared_virtual<OrdinaryMatrix>();
}
BOOST_OPENMETHOD_OVERRIDE(
transpose, (shared_virtual_ptr<const SymmetricMatrix> m),
shared_virtual_ptr<const SymmetricMatrix>) {
return m;
}
You can call a specific overrider like so:
auto u = BOOST_OPENMETHOD_OVERRIDERS(
transpose)
On Mon, Mar 10, 2025 at 9:45 PM Jean-Louis Leroy via Boost
wrote: [...] handling of const-ness `const` is supported.
Doesn't show the overloading part, but that's OK.
Quick question: Given the ability to return different (necessarily related?) types from the same open-method, can we say BOM allows co-variant return type? Like virtual methods / polymorphism allows, just more flexible that plain pointers?
Thanks, --DD

On Tue, Mar 11, 2025 at 1:50 PM Jean-Louis Leroy via Boost
Quick question: Given the ability to return different (necessarily related?) types from the same open-method, can we say BOM allows co-variant return type? Like virtual methods / polymorphism allows, just more flexible that plain pointers?
Yes. And in that case, the return type can act as a tie-breaker, as described in N2216.
Thanks again JLL. I see now this is explicitly treated in the (old) [N2216 paper][1], in fact. Regarding Boost.TypeErasure, in preparation for the review, a few words in the doc to compare/contrast them might be warranted. I look forward to the review. Thanks, --DD [1]: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2216.pdf

I endorse this library proposal. Em sáb., 8 de mar. de 2025 às 17:37, Jean-Louis Leroy via Boost < boost@lists.boost.org> escreveu:
Hello Boost developers!
I would like to propose my open-method library for inclusion in Boost.
Open-methods are an elegant solution to the Expression Problem:
Given a set of types, and a set of operations on these types, is it possible to add new operations on the existing types, and new types to the existing operations, without modifying existing code?
Open-methods solve the Expression Problem by making it possible to define free virtual functions, i.e. virtual functions that exist outside of a class. They also help reduce coupling.
Open-methods are often called "multi-methods", because they typically make it possible to select an overrider on the basis of more than one argument (as this library does). Nice, but this is a distraction. Observations show that single dispatch largely outnumbers multiple dispatch in real systems, even in languages that natively support multi-methods (Common Lisp, Clojure, Julia, Dylan, TADS, Cecil, Diesel, Nice, etc). "Multi" is just the cherry on the "open" cake.
Bjarne Stroustrup wanted open-methods in C++ almost from the beginning. In D&E he writes:
I repeatedly considered a mechanism for a virtual function call based on more than one object, often called multi-methods. [...] Since about 1985, I have always felt some twinge of regret for not providing multi-methods. Stroustrup, 1994, The Design and Evolution of C++, 13.8.
Circa 2007, he tried to bring open-methods into the standard, unfortunately without success, to his enduring regret. Decades later he writes:
In retrospect, I don’t think that the object-oriented notation (e.g., x.f(y)) should ever have been introduced. The traditional mathematical notation f(x,y) is sufficient. As a side benefit, the mathematical notation would naturally have given us multi-methods, thereby saving us from the visitor pattern workaround. Stroustrup, 2020, Thriving in a Crowded and Changing World: C++ 2006–2020.
This is what this library is all about: f(x, y), and getting rid of visitors. It closely follows the design described in the papers by Stroustrup and his PhD students Peter Pirkelbauer and Yuriy Solodkyy. Method dispatch is *fast*. It uses v-tables similar to native virtual functions, and it can match their performance. The library also provides additional features: a mechanism to call the next most specialized overrider, support for smart pointers, customization points for alternative RTTI systems, error handling, tracing, etc.
The library requires C++17 or above. It supports virtual and multiple inheritance, excluding repeated inheritance.
For evaluation purposes, I prepared a repository with a library adapted to Boost naming conventions: https://github.com/jll63/Boost.OpenMethod. The documentation is here: https://jll63.github.io/Boost.OpenMethod.
Here are a few examples from the tutorial on Compiler Explorer: * Hello World: https://godbolt.org/z/9hv8oE8zd * AST - combining virtual_ptr and std::unique_ptr: https://godbolt.org/z/cPjzfanc8 * Intrusive vptr: https://godbolt.org/z/87h9zv7dK * Policy for throwing exceptions: https://godbolt.org/z/nEYWGsPc5
If you like what you see, please consider endorsing the library.
Thanks, Jean-Louis Leroy
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
-- Alan Freitas https://alandefreitas.github.io/alandefreitas/ https://github.com/alandefreitas
participants (6)
-
Alan de Freitas
-
Dominique Devienne
-
Jean-Louis Leroy
-
Joaquin M López Muñoz
-
Ruben Perez
-
Дмитрий Архипов