[OpenMethod] Some questions

Hi Everyone, I have concerns with the design philosophy of OpenMethod library. Giving this flexibility that one person can add new open methods to a class hierarchy in one file and another person can add classes to a class hierarchy in another file independently, this is asking for miscommunication problems (programmer bugs) discovered only at runtime. I tried to recreate this situation that I expect would be common. My first question, even before I run tests is what will happen when I have a class hierarchy: struct Node { virtual ~Node(); }; struct XNode : Node {}; I declare an open method `value()` and override it for XNode in one file, adn in another file I extend the hierarchy with: struct YNode : Node {}; And then in main(), in yet another file, I will call `value()` with `YNode`. The documentation does not give me a clear answer. Section Error Handling says,
When an error is encountered, the program is terminated by a call to abort.
And then in the next sentence it talks about different debug and release policies, and ultimately, I do not know if by default abort() is called or not. But that is not the only problem. The docs use the term " When an error is encountered". What is considered "an error" here? How many situations constitute "an error"? The docs should enumerate all these situations. I need to know when my program will be calling `abort()`. So, I dun a Compiler Explorer test: https://godbolt.org/z/3fW8j4eTM And indeed, I get something that looks like abort() But when I wrap the call into a try-catch block, all of a sudden, the implementation seems to pick the wrong overrider and return a value, clearly not the intended one. And the program goes on: https://godbolt.org/z/Y6z3o56h5 This is the worst thing that can happen: a random runtime behavior. Is this a bug? Or am I missing something? Next question that naturally arises: how do I declare `noexcept` open methods with this library? Regards, &rzej;

Hi Andrzej, Thank you for your input.
When an error is encountered, the program is terminated by a call to abort.
And then in the next sentence it talks about different debug and release policies, and ultimately, I do not know if by default abort() is called or not.
The entire paragraph reads:
When an error is encountered, the program is terminated by a call to abort. If the policy contains an error_handler facet, it provides an error member function (or overloaded functions) to be called with an object identifying the error. The release and debug policies implement the error facet with vectored_error_handler, which wraps the error object in a variant, and calls a handler via a std::function. By default, it prints a description of the error to stderr in the debug policy, and does nothing in the release policy.
It is intended to say "abort is called", but in debug builds (only) you get an error message. But I can see how all the verbiage can be confusing. It is important to me that error processing be customizable. That's what policies are for. But that is also an "advanced feature". I did not want to write something that is incorrect (diagnostic in debug builds only, abort always) because error handling is customizable. The flip side is that I wrote something obscure. I am beginning to think that I should split the tutorials in two parts: Tutorials hello world multiple dispatch headers and namespaces friendship smart pointers error handling high-level explanation of what an "error" is HERE just say "abort always", with diagnostics in debug builds (don't mention policies) performance Advanced features virtual ptr alternatives core api policies custom advanced error handling custom rtti dynamic loading
So, I dun a Compiler Explorer test: https://godbolt.org/z/3fW8j4eTM And indeed, I get something that looks like abort()
What you get is a SIGSEGV, and that is because you did not register class YNode - which is the dynamic type of `n`. In release mode there are no checks, whatsoever. The perfect hash function works only with inputs from its domain. Otherwise it returns a random value. Let's fix this: https://godbolt.org/z/31abxnTPv At this point I was very surprised to still get a SIGSEGV. When I compile locally, I get a SIGABRT as expected - we are calling the method with a virtual tuple that doesn't have a matching overrider. Then I tried:
#include <stdlib.h>
int main() { abort(); return 0; }
With your choice of compiler, CE says: Program terminated with signal: SIGSEGV. See https://godbolt.org/z/c69d76feE
This is the worst thing that can happen: a random runtime behavior. Is this a bug? Or am I missing something?
I agree with you, `abort` has a random runtime behavior, SIGABRT or SIGSEGV, god(bolt) knows why, that is baaad ;-) ;-)
But when I wrap the call into a try-catch block, all of a sudden, the implementation seems to pick the wrong overrider and return a value, clearly not the intended one. And the program goes on: https://godbolt.org/z/Y6z3o56h5
Let's add an executor, shall we? https://godbolt.org/z/8sf3GsTrc Now we get:
Program returned: 139. Program stderr Program terminated with signal: SIGSEGV
For the same reason: YClass is not registered. Let's register it: https://godbolt.org/z/WqTMxhn94 ... and now we get a SIGSEGV again, which seems to be what `abort` does in these examples. Let's switch to a debug build: https://godbolt.org/z/EK5nz3daz Now we get:
Program returned: 139 Program stderr no applicable overrider for boost::openmethod::method<value_boost_openmethod (boost::openmethod::virtual_ptr<Node, > boost::openmethod::policies::debug>), int, boost::openmethod::policies::debug>(void) Program terminated with signal: SIGSEGV
Better! You did help me find a bug though. Let's reinstate the error (the missing YNode registration) and compile again in debug mode. This is wrong: https://godbolt.org/z/57xorYsGT The program is incorrect all right, but, in *debug* mode, you should get an error. Something was lost in translation from YOMM2 to OpenMethod. I created a "review" branch where I will put code and doc fixes. Here is what you get with the fix: https://godbolt.org/z/WGEn3xvG6
Program returned: 139 Program stderr unknown class YNode Program terminated with signal: SIGSEGV
...which is the intended output. I should write a troubleshooting guide.
Next question that naturally arises: how do I declare `noexcept` open methods with this library?
I took a shot at implementing noexcept support. It was not difficult. I didn't keep it because MSVC was not capable enough. And also because noexcept didn't seem hugely useful outside of specific contexts which, I believe, don't intersect much with the contexts where open-methods make sense. But I am super flexible on this, I can bring back noexcept support for capable compilers. J-L On Tue, Apr 29, 2025 at 5:30 PM Andrzej Krzemienski via Boost <boost@lists.boost.org> wrote:
Hi Everyone, I have concerns with the design philosophy of OpenMethod library. Giving this flexibility that one person can add new open methods to a class hierarchy in one file and another person can add classes to a class hierarchy in another file independently, this is asking for miscommunication problems (programmer bugs) discovered only at runtime.
I tried to recreate this situation that I expect would be common. My first question, even before I run tests is what will happen when I have a class hierarchy:
struct Node { virtual ~Node(); }; struct XNode : Node {};
I declare an open method `value()` and override it for XNode in one file, adn in another file I extend the hierarchy with:
struct YNode : Node {};
And then in main(), in yet another file, I will call `value()` with `YNode`. The documentation does not give me a clear answer. Section Error Handling says,
When an error is encountered, the program is terminated by a call to abort.
And then in the next sentence it talks about different debug and release policies, and ultimately, I do not know if by default abort() is called or not.
But that is not the only problem. The docs use the term " When an error is encountered". What is considered "an error" here? How many situations constitute "an error"? The docs should enumerate all these situations. I need to know when my program will be calling `abort()`.
So, I dun a Compiler Explorer test: https://godbolt.org/z/3fW8j4eTM And indeed, I get something that looks like abort()
But when I wrap the call into a try-catch block, all of a sudden, the implementation seems to pick the wrong overrider and return a value, clearly not the intended one. And the program goes on: https://godbolt.org/z/Y6z3o56h5
This is the worst thing that can happen: a random runtime behavior. Is this a bug? Or am I missing something?
Next question that naturally arises: how do I declare `noexcept` open methods with this library?
Regards, &rzej;
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

śr., 30 kwi 2025 o 03:16 Jean-Louis Leroy via Boost <boost@lists.boost.org> napisał(a):
Hi Andrzej,
Thank you for your input.
When an error is encountered, the program is terminated by a call to abort.
And then in the next sentence it talks about different debug and release policies, and ultimately, I do not know if by default abort() is called or not.
The entire paragraph reads:
When an error is encountered, the program is terminated by a call to abort. If the policy contains an error_handler facet, it provides an error member function (or overloaded functions) to be called with an object identifying the error. The release and debug policies implement the error facet with vectored_error_handler, which wraps the error object in a variant, and calls a handler via a std::function. By default, it prints a description of the error to stderr in the debug policy, and does nothing in the release policy.
It is intended to say "abort is called", but in debug builds (only) you get an error message.
But I can see how all the verbiage can be confusing. It is important to me that error processing be customizable. That's what policies are for. But that is also an "advanced feature". I did not want to write something that is incorrect (diagnostic in debug builds only, abort always) because error handling is customizable. The flip side is that I wrote something obscure.
I am beginning to think that I should split the tutorials in two parts:
Tutorials hello world multiple dispatch headers and namespaces friendship smart pointers error handling high-level explanation of what an "error" is HERE just say "abort always", with diagnostics in debug builds (don't mention policies) performance Advanced features virtual ptr alternatives core api policies custom advanced error handling custom rtti dynamic loading
So, I dun a Compiler Explorer test: https://godbolt.org/z/3fW8j4eTM And indeed, I get something that looks like abort()
What you get is a SIGSEGV, and that is because you did not register class YNode - which is the dynamic type of `n`. In release mode there are no checks, whatsoever. The perfect hash function works only with inputs from its domain. Otherwise it returns a random value. Let's fix this: https://godbolt.org/z/31abxnTPv
At this point I was very surprised to still get a SIGSEGV. When I compile locally, I get a SIGABRT as expected - we are calling the method with a virtual tuple that doesn't have a matching overrider.
Then I tried:
#include <stdlib.h>
int main() { abort(); return 0; }
With your choice of compiler, CE says: Program terminated with signal: SIGSEGV. See https://godbolt.org/z/c69d76feE
This is the worst thing that can happen: a random runtime behavior. Is this a bug? Or am I missing something?
I agree with you, `abort` has a random runtime behavior, SIGABRT or SIGSEGV, god(bolt) knows why, that is baaad ;-) ;-)
But when I wrap the call into a try-catch block, all of a sudden, the implementation seems to pick the wrong overrider and return a value, clearly not the intended one. And the program goes on: https://godbolt.org/z/Y6z3o56h5
Let's add an executor, shall we? https://godbolt.org/z/8sf3GsTrc Now we get:
Program returned: 139. Program stderr Program terminated with signal: SIGSEGV
For the same reason: YClass is not registered. Let's register it: https://godbolt.org/z/WqTMxhn94 ... and now we get a SIGSEGV again, which seems to be what `abort` does in these examples.
Let's switch to a debug build: https://godbolt.org/z/EK5nz3daz
Now we get:
Program returned: 139 Program stderr no applicable overrider for boost::openmethod::method<value_boost_openmethod (boost::openmethod::virtual_ptr<Node, > boost::openmethod::policies::debug>), int, boost::openmethod::policies::debug>(void) Program terminated with signal: SIGSEGV
Better!
You did help me find a bug though. Let's reinstate the error (the missing YNode registration) and compile again in debug mode. This is wrong: https://godbolt.org/z/57xorYsGT The program is incorrect all right, but, in *debug* mode, you should get an error. Something was lost in translation from YOMM2 to OpenMethod.
I created a "review" branch where I will put code and doc fixes. Here is what you get with the fix: https://godbolt.org/z/WGEn3xvG6
Program returned: 139 Program stderr unknown class YNode Program terminated with signal: SIGSEGV
...which is the intended output.
I should write a troubleshooting guide.
Next question that naturally arises: how do I declare `noexcept` open methods with this library?
I took a shot at implementing noexcept support. It was not difficult. I didn't keep it because MSVC was not capable enough. And also because noexcept didn't seem hugely useful outside of specific contexts which, I believe, don't intersect much with the contexts where open-methods make sense. But I am super flexible on this, I can bring back noexcept support for capable compilers.
Thanks JL. Continuing on the `noexcept` aspect, I think (but I didn't have a look at the implementation) that providing noexcept support is impossible, given the presence of those advanced policies for error-reporting. That is, if you allow a remote possibility of installing an error-handling policy that could throw, you automatically say that the open methods can potentially throw. And adding a conditional guarantee (noexcept only under some policies) would be even worse. I agree with your observation that noexcept, when used correctly, is used *very sparingly*: only in move constructors; and that it does not belong in open methods. I should have stated this more clearly. I simply request that the docs state explicitly that any open method is potentially throwing and you cannot declare them noexcept. Regards, &rzej;
participants (2)
-
Andrzej Krzemienski
-
Jean-Louis Leroy