std::unreachable: the message parameter

139 views
Skip to first unread message

Myriachan

unread,
Nov 22, 2017, 2:06:23 PM11/22/17
to ISO C++ Standard - Future Proposals
The consensus of the Toronto meeting's EWG was that std::unreachable, intentionally causing undefined behavior, should have two forms:

[[noreturn]] void unreachable();
[[noreturn]] void unreachable($literal string$);

The intent is similar to that of static_assert: have a diagnostic message that could be shown when an unreachable statement is executed in a debug build.

What I'm wondering is how unreachable should take that parameter.  The obvious would be the following:

[[noreturn]] void unreachable(const char *message);

This would allow literal strings, but it would also allow constructions like the following:

void f(int i) {
    switch (i) {
        case 0:
        case 2:
            something();
            break;
        default: {
            char message[64];
            std::snprintf(message, sizeof(message), "unknown value: %d", i);
            std::unreachable(message);
        }
    }
}

Should such constructions be allowed, or should a literal string be required?  If a literal string, how would that even be enforced, given that this is a library function?

In optimized builds in a mode in which the compiler does not output a diagnostic message, the call to std::snprintf could be elided by a smart compiler; if the compiler knows that the only observable effect of std::snprintf is to modify "message", it could elide the call because std::unreachable is presumably implemented as an inline function.

I was considering wording such as the following, under the assumption that a runtime value of "message" is allowed:

"If an implementation issues a diagnostic upon the execution of std::unreachable(), and the 'message' parameter is present, the diagnostic message should include the characters of 'message' that are in the execution character set."

Melissa

Arthur O'Dwyer

unread,
Nov 22, 2017, 11:10:06 PM11/22/17
to ISO C++ Standard - Future Proposals
On Wednesday, November 22, 2017 at 11:06:23 AM UTC-8, Myriachan wrote:
The consensus of the Toronto meeting's EWG was that std::unreachable, intentionally causing undefined behavior, should have two forms:

[[noreturn]] void unreachable();
[[noreturn]] void unreachable($literal string$);

The intent is similar to that of static_assert: have a diagnostic message that could be shown when an unreachable statement is executed in a debug build.

What I'm wondering is how unreachable should take that parameter.  The obvious would be the following:

[[noreturn]] void unreachable(const char *message);

Yes, that's correct.
 
This would allow literal strings, but it would also allow constructions like the following:

void f(int i) {
    switch (i) {
        case 0:
        case 2:
            something();
            break;
        default: {
            char message[64];
            std::snprintf(message, sizeof(message), "unknown value: %d", i);
            std::unreachable(message);
        }
    }
}

Should such constructions be allowed, or should a literal string be required?  If a literal string, how would that even be enforced, given that this is a library function?

I see no reason to require a literal string. The implementation of std::unreachable(const char*) would look like this on GCC and Clang:

[[noreturn]] void unreachable(const char *msg) {
    #ifdef DEBUG_MODE
        fprintf(stderr, "%s\n", msg);
        abort();
    #else
        __builtin_unreachable();
    #endif
}

Nothing here requires a literal string.

In optimized builds in a mode in which the compiler does not output a diagnostic message, the call to std::snprintf could be elided by a smart compiler; if the compiler knows that the only observable effect of std::snprintf is to modify "message", it could elide the call because std::unreachable is presumably implemented as an inline function.

Correct. In optimized builds, std::unreachable() had darn well BETTER be equivalent to __builtin_unreachable().
In debug builds, a vendor might (unlikely IMO but possible) choose to print the given message and then abort. In that case, the call to std::snprintf could not be elided by the compiler, because its output was actually being used.

I was considering wording such as the following, under the assumption that a runtime value of "message" is allowed:

"If an implementation issues a diagnostic upon the execution of std::unreachable(), and the 'message' parameter is present, the diagnostic message should include the characters of 'message' that are in the execution character set."

Assuming (correctly IMO) that the 'message' parameter is a `const char*`, it points to a null-terminated sequence of characters, which by definition are in the execution character set. The translation of a string literal (if any) from the source character set into a sequence of bytes in the execution character set has, by runtime, already happened; and therefore std::unreachable() doesn't need to do anything special to deal with it.

–Arthur

Myriachan

unread,
Nov 27, 2017, 3:36:35 PM11/27/17
to ISO C++ Standard - Future Proposals
On Wednesday, November 22, 2017 at 8:10:06 PM UTC-8, Arthur O'Dwyer wrote:

Assuming (correctly IMO) that the 'message' parameter is a `const char*`, it points to a null-terminated sequence of characters, which by definition are in the execution character set. The translation of a string literal (if any) from the source character set into a sequence of bytes in the execution character set has, by runtime, already happened; and therefore std::unreachable() doesn't need to do anything special to deal with it.

–Arthur

Should I add a form that takes a std::string_view in addition to the const char * form?  Or only have a string_view form, since std::string_view has a NUL-terminated const char * constructor?

Melissa

Arthur O'Dwyer

unread,
Nov 27, 2017, 3:56:28 PM11/27/17
to ISO C++ Standard - Future Proposals
My impression is that LEWG has been unfavorable to proposals that complicate overload resolution for a name with both (const char*) and (std::string_view) signatures. The (a?) problem is that it seems plausible for a user-defined type to have implicit conversions to both types, in which case the overload resolution would be ambiguous. There are well-known solutions to the problem of "assigning priorities to otherwise ambiguous overloads" (and Concepts will give us yet another solution)... but if we avoid causing the problem, we don't even need to solve it.

I would confidently suggest that you should provide only (const char *), if you feel the need for a message parameter at all. (I don't feel that need, myself.)
- Consider that the expected input is that the user will pass a string literal, which is exactly (const char *) and converting to string_view would be superfluous.
- Consider that the expected usage-of-that-input is that the implementation will pass it to fputs(stderr) or some other I/O mechanism on the same approximate level of complexity as abort(). These I/O mechanisms often natively take (const char *), and often cannot quite as easily handle non-null-terminated (std::string_view).

The problem is not converting a null-terminated-byte-string to string_view; the problem is converting a string_view into a null-terminated-byte-string for consumption by the downstream C runtime. Similar considerations (but more extreme) are why (Jonathan Wakely told me) that `int std::svtoi(std::string_view)` will never happen. It would be useful functionality to have, but it would be too complicated to implement. `std::stoi` can (and does) just piggyback on top of `strtol`; but `std::svtoi` would need a brand-new implementation that could handle non-NTBS.

–Arthur

Thiago Macieira

unread,
Nov 27, 2017, 6:53:24 PM11/27/17
to std-pr...@isocpp.org
On Monday, 27 November 2017 12:56:25 PST Arthur O'Dwyer wrote:
> - Consider that the expected usage-of-that-input is that the implementation
> will pass it to fputs(stderr) or some other I/O mechanism on the same
> approximate level of complexity as abort(). These I/O mechanisms often
> natively take (const char *), and often cannot quite as easily handle
> non-null-terminated (std::string_view).

Actually, in this case it is easy to handle non-null-terminated:

fwrite(msg, msg.size(), 1, stderr);

But only in this one case. If you wanted to use one of many other logging
tools, like Win32's OutputDebugString(), syslog(), or sd_journal_send(), you'd
be right.

--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center

Reply all
Reply to author
Forward
0 new messages