[system] A non-allocating message() overload
One of the objections against
On Wed, Sep 19, 2018 at 11:57 AM Peter Dimov via Boost < boost@lists.boost.org> wrote:
One of the objections against
is that message() needs to allocate memory, because it returns an std::string. This is true even for user-defined categories that can implement it without allocating, with a static message table. To address this, I'm thinking of adding the following overload to boost::system::error_category (with the intent of proposing it for std::error_category):
virtual char const* message( int ev, char* buffer, size_t len ) noexcept;
This is modeled after the glibc-specific version of strerror_r (and in fact has the exact same signature and behavior). If the implementation has a static character literal corresponding to `ev`, it returns it directly. If not, it composes an error message into the provided buffer, then returns `buffer`.
Comments?
I *really* like this interface (pass in buffer-to-be-populated, or return internal static buffer). Am I correct in assuming the user could then: ... ec = ...; char* my_buf = ...; size_t my_buf_size = ...; char const* msg = ec.message(SOME_ERR_ENUM, my_buf, my_buf_size); if(msg != my_buf) { // ...delete "my_buf", it is not needed } // TRUST that "msg" is still valid (must reference static-internal-buffer, or is "my_buf" Question: Is there a *guarantee* that 'message()' never return 'nullptr'? (Is that ever desired?) --charley
charleyb123 wrote: ...
To address this, I'm thinking of adding the following overload to boost::system::error_category (with the intent of proposing it for std::error_category):
virtual char const* message( int ev, char* buffer, size_t len ) noexcept;
This is modeled after the glibc-specific version of strerror_r (and in fact has the exact same signature and behavior). If the implementation has a static character literal corresponding to `ev`, it returns it directly. If not, it composes an error message into the provided buffer, then returns `buffer`.
Comments?
I *really* like this interface (pass in buffer-to-be-populated, or return internal static buffer).
Am I correct in assuming the user could then:
... ec = ...; char* my_buf = ...; size_t my_buf_size = ...; char const* msg = ec.message(SOME_ERR_ENUM, my_buf, my_buf_size); if(msg != my_buf) { // ...delete "my_buf", it is not needed }
// TRUST that "msg" is still valid (must reference static-internal-buffer, or is "my_buf"
Yes, this would be guaranteed.
Question: Is there a *guarantee* that 'message()' never return 'nullptr'? (Is that ever desired?)
The only failure mode would be when the function needs to compose a message and the provided buffer is too small to hold it; I'll have to check what strerror_r(12345, buffer, 1) (for instance) does but in my opinion nullptr should not be a valid return value; in this case something like truncation or returning "Insufficient buffer size for message" would be my preference.
charleyb123 wrote:
Question: Is there a *guarantee* that 'message()' never return 'nullptr'? (Is that ever desired?)
The only failure mode would be when the function needs to compose a message and the provided buffer is too small to hold it; I'll have to check what strerror_r(12345, buffer, 1) (for instance) does but in my opinion nullptr should not be a valid return value; in this case something like truncation or returning "Insufficient buffer size for message" would be my preference.
On reflection, I take that back. nullptr will be a valid return value, but only in the case where you pass nullptr as the buffer (and, accordingly, 0 as the length.) That is, the function should not "fail" with "Insufficient buffer size for message", it will just fill the buffer and truncate, even when the buffer size is something ridiculously small such as 2, 1, or even 0. This enables the (apparently idiomatic when using strerror_r) technique of using `message( ev, nullptr, 0 )` to detect whether the message is static, and do something else if it isn't.
charleyb123 wrote:
Question: Is there a *guarantee* that 'message()' never return 'nullptr'? (Is that ever desired?)
The only failure mode would be when the function needs to compose a message and the provided buffer is too small to hold it; I'll have to check what strerror_r(12345, buffer, 1) (for instance) does but in my opinion nullptr should not be a valid return value; in this case something like truncation or returning "Insufficient buffer size for message" would be my preference.
Peter Dimov respondeth:
On reflection, I take that back. nullptr will be a valid return value, but only in the case where you pass nullptr as the buffer (and, accordingly, 0 as the length.)
That is, the function should not "fail" with "Insufficient buffer size for message", it will just fill the buffer and truncate, even when the buffer size is something ridiculously small such as 2, 1, or even 0.
This enables the (apparently idiomatic when using strerror_r) technique of using `message( ev, nullptr, 0 )` to detect whether the message is static, and do something else if it isn't.
Ah, that had not occurred to me. Ok, yes, that could be useful (although probably uncommon). I previously could think of no use for returning 'nullptr', but I accept this as the (possibly only) scenario where that might be useful. Not to wander, but does this then suggest there might be interest in the two-call idiom to detect the buffer-size *required*, so the caller can allocate? (This has become increasingly popular such as in the Vulkan APIs, and Microsoft create-process/console functions), for example see: https://stackoverflow.com/questions/37662614/calling-vkenumeratedeviceextens... ...or more simply: int count = 0; vkGetSwapchainImagesKHR(device, swapchain, &count, nullptr); // (e.g.) return count = 3. VkImage images[3]; vkGetSwapchainImagesKHR(device, swapchain, &count, images); // (e.g.) return ID = 4,5,6 --charley
charleyb123 wrote:
Not to wander, but does this then suggest there might be interest in the two-call idiom to detect the buffer-size *required*, so the caller can allocate? (This has become increasingly popular such as in the Vulkan APIs, and Microsoft create-process/console functions), for example see: [...]
This is fairly idiomatic in MS land, but for this specific API, neither strerror_r nor FormatMessage give you a length, and user implementations typically won't be able to either (you compose the message with something like snprintf and it doesn't give you the final length either.) And it's not that critical. Messages are typically bounded by something reasonable and truncation is not that big of a problem.
On 9/19/18 8:56 PM, Peter Dimov via Boost wrote:
One of the objections against
is that message() needs to allocate memory, because it returns an std::string. This is true even for user-defined categories that can implement it without allocating, with a static message table. To address this, I'm thinking of adding the following overload to boost::system::error_category (with the intent of proposing it for std::error_category):
virtual char const* message( int ev, char* buffer, size_t len ) noexcept;
This is modeled after the glibc-specific version of strerror_r (and in fact has the exact same signature and behavior). If the implementation has a static character literal corresponding to `ev`, it returns it directly. If not, it composes an error message into the provided buffer, then returns `buffer`.
Comments?
I like the idea, but what about platforms that don't have strerror_r? noexcept means that the new overload cannot be implemented atop the standard one (e.g. one can't forward the call to the current std::error_category::message).
Andrey Semashev wrote:
virtual char const* message( int ev, char* buffer, size_t len ) noexcept;
I like the idea, but what about platforms that don't have strerror_r?
All platforms should be able to implement this interface; you could for instance FormatMessageA into the provided buffer directly, or call strerror and then copy the string into the buffer, and so on.
noexcept means that the new overload cannot be implemented atop the standard one (e.g. one can't forward the call to the current std::error_category::message).
For compatibility reasons (if we make this function pure this will break all existing user categories) we'll have to provide a default implementation in terms of the string-returning one (even though the reverse would be more natural.) It would be something like try { string msg = message(ev); strlcpy(buffer, msg.c_str(), len); return buffer; } catch(...) { return "Message unavailable"; }
participants (3)
-
Andrey Semashev
-
charleyb123
-
Peter Dimov