GotW #97 Solution: Assertions (Difficulty: 4/10)

Assertions have been a foundational tool for writing understandable computer code since we could write computer code… far older than C’s assert() macro, they go back to at least John von Neumann and Herman Goldstine (1947) and Alan Turing (1949). [1,2] How well do we understand them… exactly?

1. What is an assertion, and what is it used for?

An assertion documents the expected state of specific program variables at the point where the assertion is written, in a testable way so that we can find program bugs — logic errors that have led to corrupted program state. An assertion is always about finding bugs, because something the programmer thought would always be true was found to actually be false (oops).

For example, this line states that the program does not expect min to exceed max, and if it does the code has a bug somewhere:

// Example 1: A sample assertion

assert( min <= max );

If in this example min did exceed max, that would mean we have found a bug and we need to go fix this code.

GUIDELINE: Assert liberally. [3] The more sure you are that an assertion can’t be false, the more valuable it is if it is ever found to be false. And in addition to finding bugs today, assertions verify that what you believe is “obviously true” and wrote correctly today actually stays true as the code is maintained in the future.

GUIDELINE: Asserted conditions should never have side effects on normal execution. Assertions are only about finding bugs, not doing program work. And asserted conditions only evaluated if they’re enabled, so any side effects won’t happen when they’re not enabled; they might sometimes perform local side effects, such as to do logging or allocate memory, but the program should never rely on them happening or not happening. For example, adding an assertion to your code should never make a logically “pure” function into an impure function. (Note that “no side effects on normal execution” is always automatically true for violation handlers even when an assertion system such as proposed in [4] allows arbitrary custom violation handlers to be installed, because those are executed only if we discover that we’re in a corrupted state and so are already outside of normal execution. [5] For conditions, it’s up to us to make sure it’s true.)

2. C++20 supports two main assertion facilities… For each one, briefly summarize how it works, when it is evaluated, and whether it is possible for the programmer to specify a message to be displayed if the assertion fails.

assert

The C-style assert is a macro that is evaluated at execution time (hopefully enabled at least during your unit testing! see question 4) if NDEBUG is not set. The condition we pass it must be a compilable boolean expression, something that can be evaluated and converted to bool.

It doesn’t directly support a separate message string, but implementations will print the failed condition itself, and so a common technique is to embed the information in the condition itself in a way that doesn’t affect the result. For example, a common idiom is to append &&"message"  (a fancy way of saying &&true):

// Example 2(a): A sample assert() with a message

assert( min <= max
        && "BUG: argh, miscalculated min and max again" );

static_assert

The C++11 static_assert is evaluated at compile time, and so the condition has to be a “boolean constant expression” that only refers to compile-time known values. For example:

// Example 2(b): A sample static_assert() with a message

static_assert( sizeof(int) >= 4,
               "UNSUPPORTED PLATFORM: int must be at least 4 bytes" );

It has always supported a message string, and C++17 made the message optional.

Bonus: [[assert: ?

Looking forward, a proposed post-C++20 syntax for assertions would support it as a language feature, which has a number advantages including that it’s not a macro. [4] This version would be evaluated at execution time if checking is enabled. Currently that proposal does not have an explicit provision for a message and so programmers would use the && "message" idiom to add a message. For example:

// Example 2(c): An assertion along the lines proposed in [4]

[[assert( min <= max
          && "BUG: argh, miscalculated min and max again" )]] ;

3. If an assertion fails, what does that indicate, and who is responsible for fixing the failure?

A failed assertion means that we checked and found the tested variables to be in an unexpected state, which means at least that part of the program’s state is corrupt. Because the program should never have been able to reach that state, two things are true:

  • There is a program bug, possibly in the assertion itself. The first place to look for the bug is in this same function, because if prior contracts were well tested then likely this function created the first unexpected state. [5]
  • The program cannot recover programmatically by reporting a run-time error to the calling code (via an exception, error code, or similar), because by definition the program is in a state it was not designed to handle, so the calling code isn’t ready for that state. It’s time to terminate and restart the program. (There are advanced techniques that involve dumping and restarting an isolated portion of possibly tainted state, but that’s a system-level recovery strategy for an impossible-to-handle fault, not a handling strategy for run-time error.) Instead, the bug should be reported to the human developer who can fix the bug.

GUIDELINE: Don’t use assertions to report run-time errors. Run-time errors should be reported using exceptions, error codes, or similar. For example, don’t use an assertion to check that a remote host is available, or that the user types valid input. Yes, std::logic_error was originally created to report bugs (logic errors) using an exception, but this is now widely understood to be a mistake; don’t follow that pattern.

Referring to this example:

// Example 3

void f() {
    int min = /* some computation */;
    int max = /* some other computation */;

    // still yawn more yawn computation

    assert( min <= max );         // A

    // ...
}

In this code, if the assertion at line A is false, that means what the function actually did before the assertion doesn’t match what the assertion condition expected, so there is a bug somewhere in this function — either before or within the assertion.

This demonstrates why assertions are primarily about eliminating bugs, which is why we test…

4. Are assertions primarily about checking at compile time, at test time, or at run time? Explain.

Assertions are primarily about finding bugs at test time. But assertions can also be useful at other times because of some well-known adages: “the sooner the better,” “better late than never,” and “never [will] I ever.”

Bonus points for pointing out that there is also a fourth time in the development cycle I didn’t list in the question, when assertions can be profitably checked. Here they are:

Of course this can be even more nuanced. For example, you might make different decisions about enabling assertions if your “run time” is an end user’s machine, or a server farm, or a honeypot. Also, checking isn’t free and so you may enable run-time checking for severe classes of bugs but not others, such as that an operating system component may require checking in production for all out-of-bounds violations and other potential security bugs, but not non-security classes of bugs.

First, “the sooner the better”: It’s always legal and useful to find bugs as early as possible. If we can find a bug even before actually executing a compiled test, then that’s wonderful. This is a form of shift-left. We love shift-left. There are two of these times in the graphic:

  • (Earliest, best) Edit time: By using a static analysis tool that is aware of assert and can detect some violations statically, you can get some diagnostics as you’re writing your code, even before you try to compile it! Note that to recognize the assert macro, you want to run the static analyzer in debug mode; analyzers that run after macro substitution won’t see an assert condition when the code is set to make release builds since the macro will expand to nothing. Also, usually this kind of diagnostic uses heuristics and works on a best-effort basis that catches some mistakes while not diagnosing others that look similar. But it does shift some diagnostics pretty much all the way left to the point where you’re actually writing the code, which is great when it works… and you still always have the next three assertion checking times available as a safety net.
  • (Early) Compile time: If a bug that depends only on compile-time information can be detected at compile time even before actually executing a compiled test, then that’s wonderful. This is one reason static_assert exists: so that we can express tests that are guaranteed to be performed at compile time.

Next, the primary target:

  • Test time: This is the main time tests are executed. It can be during developer-machine unit testing, regression testing, build-lab integration testing, or any other flavor of testing. The point is to find bugs before they escape into production, and inform the programmer so they can fix their bug.

Finally, “better late than never” (safety net) or “never [will] I ever” (intolerable severe condition):

  • (Late) Run time: Even after we release our code, it can be useful to have a way to enable checking at run time to at least log-and-continue (e.g., using facilities such as [6] or [7]). One motivation is to know if a bug made it through testing and out into the field and get better late-debug diagnostics; this is sometimes called shift-right but I think of it as much as being about belt-and-suspenders. Another motivation is to ensure that severe classes of bugs ensure execution will halt outright if we cannot tolerate continuing after such a fault is detected.

Importantly, in all cases the motivation is still debugging: Findings bugs early is still debugging, just better (sooner and less expensive). And finding bugs late that escaped into production is still debugging, just worse (later and more expensive). Each of these times is a successive safety net for bugs that make it past the earlier times.

Because at run time we may want to log a failed assertion, our assertion violation handler should be able to USE-A logging system, but the relationship really is USES-A. An assertion violation handling system IS-NOT-A general-purpose logging system, and so a contracts language feature shouldn’t be designed around such a goal. [5]

Finally, speaking of run time: Note that it can be useful to write an assertion, and also write code that does some handling if the assertion is false. Here’s an example from [8]:

// Example 4: Defense in depth

int DoSomething(int x) {

    assert( x != 0 && "x should be nonzero" ); // finds bug, if checked
    if( x == 0 ) {
        return INVALID_COOKIE; // robustness fallback, if not checked
    }

    // do useful work

}

You might see this pattern written interleaved as follows to avoid duplicating the condition, and this is one of the major patterns that leads to writing assert(!"message"):

    if( x == 0 ) {
        assert( !"x should be nonzero" ); // finds bug, if checked
        return INVALID_COOKIE; // robustness fallback, if not checked
    }

At first this may look like it’s conflating the distinct “bug” and “error” categories we saw in Question 3’s table. But that’s not the case at all, it’s actually deliberately using both categories to implement “defense in depth”: We assert something in testing to minimize actual occurrences, but then in production still provide fallback handling for robustness in case a bug does slip through, for example if our test datasets didn’t exercise the bug but in production we hit some live data that does.

Notes

With thanks to Wikipedia for the first two references.

[1] H. H. Goldstine and J. von Neumann. “Planning and Coding of problems for an Electronic Computing Instrument” (Report on the Mathematical and Logical Aspects of an Electronic Computing Instrument, Part II, Volume I, p. 12; Institute for Advanced Study, April 1947).

[2] Alan Turing. “Checking a Large Routine” (Report of a Conference on High Speed Automatic Calculating Machines, pp. 67-9, June 1949).

[3] H. Sutter and A. Alexandrescu. C++ Coding Standards (Addison-Wesley, 2004). Item 68, “Assert liberally to document internal assumptions and invariants.”

[4] G. Dos Reis, J. D. Garcia, J. Lakos, A. Meredith, N. Myers, and B. Stroustrup. “P0542: Support for contract based programming in C++” (WG21 paper, June 2018). To keep it as legal compilable (if unenforced) C++20 for this article I modified the syntax from : to ( ). That’s not a statement of preference, it’s just so the examples can compile today to make them easier to check.

[5] Upcoming GotWs will cover postconditions, preconditions, invariants, and violation handling.

[6] G. Melman. spdlog: Fast C++ logging library (GitHub).

[7] Event Tracing for Windows (ETW) (Microsoft, 2018).

[8] H. Sutter. “P2064: Assumptions” (WG21 paper, 2020).

Acknowledgments

Thank you to the following for their comments on drafts of this article: Joshua Berne, Gábor Horváth, Andrzej Krzemieński, Andrew Sutton. Thanks also to Reddit user “evaned” and Anton Dyachenko for additional feedback.

18 thoughts on “GotW #97 Solution: Assertions (Difficulty: 4/10)

  1. Thanks Herb for the article!
    I have a question for the Example 4. Let’s assume we have a unit test for `DoSomething` testing for x == 0, expecting that INVALID_COOKIE is returned. When compiled in Debug (having asserts enabled), the unit test cannot complete due to the assert being triggered.
    How to deal with this scenario? Should the unit tests run only on the code compiled with asserts disabled? Shouldn’t the unit tests cover the invalid parameters scenarios at all?
    Thanks!

  2. To me, the most important kind of assertion is the “class invariant”; unfortunately, regular assertions are not especially useful for those. Is there any talk of a way to support assertions to be checked on entry and exit of every public method?

  3. An assertion failure always means “programmer error”. So one of two things must be true
    1) With or without assertions, DoSomething must never be called with x ==0
    2) When DoSomething is called with x==0, it is handled as some sort of runtime error, and no assertion is involved.

    You have to decide : Is calling DoSomething with x==0 a bug, or simply an error?

  4. avjewe wrote:
    > To me, the most important kind of assertion is the “class invariant”; unfortunately, regular assertions are not especially useful for those. Is there any talk of a way to support assertions to be checked on entry and exit of every public method?

    Wouldn’t a member assertInvariant() do the job? You could just call it whenever you think the invariant should hold.

  5. What about assertions/contracts in test code ?
    Should test framework assertions be used ?

  6. Of course that’s possible, but I was hoping for something more automatic. There’s a big difference between pasting “assertInvariant()” in many many places (which is what I do now), and having the compiler assure me that it’s called in every appropriate situation, without cluttering the code.

  7. Checking invariant has an cost. And in C++, you must be able to control the costs.

  8. “The program cannot recover programmatically by reporting a run-time error to the calling code, because by definition the program is in a state it was not designed to handle, so the calling code isn’t ready for that state. It’s time to terminate and restart the program.”

    Is this really true? As a user, I do not want that after updating some program suddenly crashes in the new release and lose all my work because I tried some new feature that was recently added and is not so well tested yet. As a user, i would prefer a message like “Sorry, this feature does not yet work, please send a bug report” and then continue with my work as usual (not yet using the new feature).

  9. I think you contradict to yourself by promoting defensive programming and shaming throwing logic error in the same article.

    In your example of defensive programming you clearly fullish yourself by using normal runtime error code in case of a bug. If you want to return an error code in case of a bug then never ever use codes designed for normal/real runtime errors – use code that indicates bug/logic error. As your coworker I would have to ask is it really a bug if you report runtime error? What happened if I simply remove assert? – yes, nothing, code continue to work by reporting runtime error. Reporting runtime error indicates that state of your program is OK, but this is not true in case of a real bug.

    Any distinct error code must have distinct handling logic, otherwise it is redundant, therefore, logic error code should be handled differently then normal runtime error, and of course due to state of your program is NOT ok. It is project depending how to handle it and if it makes any sense of doing defensive programming at all.

    Now simply replace error code by exception in the text above and everything keeps sence. If you blame throwing logic error you must be coherent and blame defensive programming as it just another implementation of the same idea. You say defensive programming is ok then you should aknowledge that exception is just another machinery doing it.

    Simple example: if you will use RAII for local/stack state and transaction idiom for non local state then throwing an exception will recover yor state up to safe point. I have used logic_error exceptions instead of error codes for defensive programming in UI(event based) application catching logic error exception in the main event loop only. This allows user to save time on application restart and manual reproduction of the lost state in case of a conventional termination. Only the last operation is terminated and state recover to the point if user never activates a chain of functionality that includes a bug.

    If anybody provides defensive programming for bugs (either codes or exception) they should fix expected behaviour provided by defensive technique under test. That is why defensive programming is your last resort it is hard to implement (harder then normal runtime error, due to unclear safe state boundaries) and hard to test you need to handle conditional compilation. Do it only if benefits for USERS (not for developers) outperform costs, otherwise conventional termination is your choice.

  10. While I mostly agree, from experience, asserting inside of services is very problematic.
    If you leave those asserts in for production, you expose yourself to query of death. If you don’t you exposing yourself to data corruptions, since these assertions create the illusion that these corner cases are actually handled.

    For anything that is expected to run continually, always return codes / exceptions > assertions.

  11. @Cristi: Right, the unit tests would have to include a build where the assertion is disabled for testing that path.

    @avjewe: We are on our way to discussion invariants! My plan for this GotW series is to cover assertions (here), postconditions (next), preconditions, and then invariants, because they successively build upon each other and are in that order of increasing difficulty. As D_Coder pointed out, invariant checking has a well-worn pitfall in contracts used by many major languages, which is that checking them on entry/exit of every public function is actually wrong… we’ll cover that in the “class invariants” GotW.

    Also, re this and also to @Anton’s related question:
    > You have to decide : Is calling DoSomething with x==0 a bug, or simply an error?

    The key point of Example 4 is that the technique is intentionally “defense in depth” — so yes it’s unambiguously a bug (else we wouldn’t assert it), but we _also_ choose to have a fallback path even when bugs are not checked. This is not the same as using an exception to report a programmer bug, it’s implementing a fallback to continue even after a programming bug has been detected if we cannot tolerate terminating at that time. Note this should not be a common pattern, it should be rare — but it exists and is useful which is why I mentioned it.

    @D_Coder: Right, defining “what contracts should be enabled when testing ” is an upcoming topic, once we’ve covered all the four kinds of contracts.

    @Helmut: Right, that’s when you would use a log-and-continue violation handler. I alluded to this in “(Late) Run time” above, but there will also be a GotW specifically about violation handlers.

  12. > You have to decide : Is calling DoSomething with x==0 a bug, or simply an error?

    error_code DoSomething(int x)
    {
    if(x == 0) return error_code::runtimeErrrorN; // simply an error
    if(x == 0) return error_code::logicError; // a bug
    }

    if(DoSomething(0) == runtimeErrrorN)
    {
    // our program state is OK, don’t need to attempt any recovery, can use any variable without UB
    }

    if(DoSomething(0) == logicError)
    {
    // DANGER, state of the program is NOT ok (at least partially)
    // if you want/need to proceed you HAVE TO provide some recovery logic which is hard and not always possible
    // usually it is a simple return in case of error codes up to the very high-level function (event loop)
    // which can rollback changes that is already done with non-local state caused by user action
    return logicError;
    }

    ideally, instead of error_code, use std::variant if you need to handle bugs via error codes, expected if you don’t have to handle bugs. My personal choice is

    expected DoSomething(int x) THROW_IN_PUBLIC_BUILD_IF_CONTRACT_BROKEN
    {
    REQUIRE(x != 0, “this is a bug”); // yes, macros, as with committee very slow pace of work
    // we are going to have contracts in c++3x and, of course, by a very ancient tradition with wrong defaults
    // and probably dead-end design that doesn’t fit anybody (remember future, template exports, uncaught_exception, and many others)
    }

    #if PUBLIC_RELEASE
    #define THROW_IN_PUBLIC_BUILD_IF_CONTRACT_BROKEN
    #else
    #define THROW_IN_PUBLIC_BUILD_IF_CONTRACT_BROKEN noexcept
    #endif

    // do you wondering why do I need a lambda? To shut up compiler warning about throw in a noexcept function
    #define CHECK_CONTRACT(reason, condition, …) [&] \
    { \
    condition \
    ? static_cast(0) \
    : throw MAKE_LOGIC_ERROR(reason, condition, __VA_ARGS__); \
    }

    #define REQUIRE (condition, …) CHECK_CONTRACT(Precondition failed, condition, __VA_ARGS__)()

    the rest is not provided as irrelevant details.

    I think it is clear that in a non-public release it is well defined terminate and in a release, it is an exception caught in event loop. My code currently doesn’t allow to switch contracts off in any build but this is not impossible to implement, Although I don’t have reasons for doing this in my projects I can understand that sometimes every CPU cycle does matter and we are fine to trade of risks over speed. And of course, using THROW_IN_PUBLIC_BUILD_IF_CONTRACT_BROKEN is an exceptional case, the default choice is unconditional noexcept. having THROW_IN_PUBLIC_BUILD_IF_CONTRACT_BROKEN I am able to tune behavior per function granularity.

    Actually, performance characteristics of exceptions don’t keep any other genuine choice than logic error handling/defensive programming to use them in the code.

    The only reason I am writing it here is to leverage a chance to change your mind (you are quite a respectful person in c++ world deservedly) about contract design and hoping you can push the design of c++ contracts in the “proper” direction. To recap what is a proper direction:
    * tunable reaction in case of contract violation, ideally, up to per each contract: default termination, alternative exception, maybe some other options
    * tunable check or not check contracts, ideally, up to per each contract: default always check, alternative do not check
    * please, please, PLEASE!!!!!! capture stack trace on failed contract + location + message. Don’t say to me this is not possible (C#) or this is not the purpose of contracts – of course, it is – ease of finding bugs.
    * an option to specify contracts that are ALWAYS checked regardless of any settings. Something like validate.

    The last point is REALLY needed to avoid boilerplate code like

    const auto result = DoSomething(); // oops, side effect
    if(result != ok)
    {
    assert(false && “this is bug, and the only reason we don’t use assert is that somebody decided that asserts only for test time – stupid idea, they wanted me to exercise in typing rather than giving to me a proper tool”);
    }

    instead there should be something like
    std::validate(DoSomething() == ok, “this is a bug”); // always evaluated – no way to disable, but should capture this location instead of std::validate function in the error message

    The reason for this is lots of developers including VERY mature sometimes forget about assert behavior in release and still use it for code with side effects, and that is not THEIR mistake, it is a mistake in assert design. I bet 100% nubies done this mistake so who is wrong independent set of people regularly doing this mistake or some “guru” who decided – asserts is not intended for code with side effects and therefore checked in test time only?

    Just an example

    [[x != 0, “this is a bug”, fail = public_release ? std::error_logic : std::terminate]]
    expected DoSomething(int x) noexcept(!public_release)

    [[x != 0, “lets hope this is always true”, check = !public_release]]
    expected DoSomethingVeryFast(int x) noexcept

    expected DoSomethingVeryFast(int x) noexcept(!public_release)
    {
    [[contract: fail = public_release ? std::error_logic : std::terminate]]
    std::validate(side-effect(x), “this is a bug”);
    }

  13. > Yes, std::logic_error was originally created to report bugs (logic errors) using an exception, but this is now widely understood to be a mistake; don’t follow that pattern.

    Wouldn’t it make a natural defense-in-depth backup to assertions?
    Even when there is no reasonable behavior to default to, calling code might prefer to prompt user in lines of “Programming error detected. Wanna try saving progress before termination?” instead of corrupting program state quietly.

  14. Assertions are used to find bugs in the program. In an ideal world, the program is proven correct: assertions are thus useless. In the real world, however, proving a program is often an impossible task. Assertions can be used to put barriers during static analysis (usually that’s what preconditions will do, restrict the flow analysis to expected values), or on the other hand used to check that the code is able to ensure the assertion (check using flow analysis that given the preconditions and the code, the postconditions are always met). And of course, as a last resort they can be checked at runtime.

    An interesting point not raised here is that codes actually runs on real hardware, and not on a theoric perfect machine. Thus, even if the program is proven correct, some assertions can actually not being met in the real execution because the hardware is buggy (not so uncommon, we all remember the infamous pentium fpu bug) or operating outside its specification (magnetic interferences, overheating, etc.). The compiler can also be itself buggy and generate wrong code. If i am starting a life critical operation, i really wish to actually check that all my preconditions are met, even if my program is proven correct. This is a strong use case for “in-depth defensive programming”, and for enabling some run-time assertions.

    A possible categorization of assertions would be :
    * provable (1), either at compile-time or static-analysis time (i’m not fond of the distinction made between these two in this article, it exists primarily because they usually are different tools, but compilation include a static analysis phase and this is this part that triggers static_assert). This includes static_assert but also several type safety checks (such as gsl::not_null)
    * too expensive to prove (2), the assertion can not reasonably be proved, so we rely upon runtime detection and testing to make sure there are no issues with the code. Unprovable, from a practical perspective, is the same as too expensive to prove.
    * needed at runtime (3), the assertion is there to verify that there are no issues with the environment the program is running in.

    The assert macro is in the second category. static_assert is only in first. Contracts, on the other hand, will probably fit in both category 1 and 2, depending on their complexity (static analysis tool will improve, allowing us to check some of them at compile time). There is AFAIK no standard mechanism for the third category.

  15. @Anton:

    > * tunable reaction in case of contract violation, ideally, up to per each contract: default termination, alternative exception, maybe some other options

    We’ll cover contract violations later in this GotW series. What I’ll write is compatible with most of what you say, except I’ll point out why contract violations should not report run-time errors (including and especially by throwing exceptions). For one thing, it would mean you can’t use a contract in a noexcept function, but contracts and noexcept must be orthogonal — a function could choose to use contract, and/or choose to be noexcept, and those decisions are logically orthogonal and independent. More on that when we get to the GotW, but this GotW purposely laid the groundwork with “GUIDELINE: Don’t use assertions to report run-time errors.”

    > * tunable check or not check contracts, ideally, up to per each contract: default always check, alternative do not check
    > * an option to specify contracts that are ALWAYS checked regardless of any settings. Something like validate.

    We’ll start seeing more about both of these in the next one, #98.

    > * please, please, PLEASE!!!!!! capture stack trace on failed contract + location + message. Don’t say to me this is not possible (C#) or this is not the purpose of contracts – of course, it is – ease of finding bugs.

    The proposal in [4] allows for a source_location and stack trace. Those should be opt-in though — zero-overhead principle, pay only for what you use.

    @juhani:

    > Wouldn’t [logic_error] make a natural defense-in-depth backup to assertions?

    It could, though normally defense-in-depth would log-and-continue. The example of “error detected, wanna save before terminating?” doesn’t feel like defense-in-depth though (it’s neither log-and-continue nor log-and-throw), that feels like installing a violation handler that does some last-ditch stuff before it still terminates.

  16. I read through the documents you have provided via links:

    “17. If a user-provided violation handler exits by throwing an exception and a contract is violated on a call to a function with a non-throwing exception specification, then the behavior is as if the exception escaped the function body”

    which is somehow contradicts to

    “2.10 Invoking the handler
    The proposal does not support the direct invocation of the violation handler. Allowing so, would imply access to handler supplied by the user.

    Previous versions of this proposal included an always assertion level. That level was introduced as a way to make it possible to invoke the violation handler. However, the mechanism seemed to be problematic when used in interfaces. Addtionally, it resulted to be more controversial and not addressing exactly initial intent.

    Consequently, we have removed the always level from the current proposal and we may revisit alternative solutions in the future.”

    Could you please elaborate about this “the mechanism seemed to be problematic when used in interfaces. Addtionally, it resulted to be more controversial and not addressing exactly initial intent.” or maybe you can provide mail lists and/or links where I can read more. Also would be nice to use the same terms everywhere – in explanatory place it is assertion-level and the formal part it is contract-level.

    I see that if you would keep always(contract-level) I could implement throwing logic_error by providing my handler that simply throws std::contract_violation_exception.

    namespace std
    {
    enum class violation_continuation_modes
    {
    on,
    off
    };

    constexpr violation_continuation_modes violation_continuation_mode = implementation defined value;

    template
    void contract_violation_handler(const const std::contract_violation& violation) noexcept
    {
    if constexpr(on) return;

    // implementation defined use of violation
    ….

    std::terminate();
    }

    constexpr auto contract_violation_handler_ptr = implementation defined value, by default contract_violation_handler

    // pseudo function that should produce code like this, or I can implement it as macro
    void validate(assertion_expression, optional message_expression)
    {
    if (assertion_expression) return;

    const auto info = contract_violation
    {
    location = caller_location,
    assertion = “assertion_expression”,
    comment = std::format(message_expression),
    assertion_level = “always”
    };
    contract_violation_handler_ptr(info);
    }

    class contract_violation_exception : public std::logic_error
    {
    public:
    contract_violation_exception(const contract_violation& violation) noexcept;
    const contract_violation& violation() const noexcept;

    };
    }

    constexpr auto throwInPublicRelease = std::violation_continuation_mode == std::violation_continuation_modes::on;
    [[noreturn]] void my_handler(const const std::contract_violation& violation) noexcept(!throwInPublicRelease)
    {
    throw std::contract_violation_exception(violation);
    }

    void doSomething(int x) noexcept(!throwInPublicRelease)
    [[expects: x != 0]]
    {
    std::validate(possibly-side-effect(x), “x: {}”, x);
    }

    cc my.cpp /contract_handler=path_to_my_handler.cpp:my_handler /continuation_mode=on /contract_level=off

    And you still have not provided any solid foundation apart from “std::logic_error was originally created to report bugs (logic errors) using an exception, but this is now widely understood to be a mistake”. Again I still don’t understand why you believe returning error code in the exact same case (where assert would fail == bug == broken state) is fine while throwing an exception is not. The only argument you provided that has some sense is noexcept issue, but this is trivially to solve having continuation mode constant available from source code.

  17. @Herb:

    > It could, though normally defense-in-depth would log-and-continue. The example of “error detected, wanna save before terminating?” doesn’t feel like defense-in-depth though (it’s neither log-and-continue nor log-and-throw), that feels like installing a violation handler that does some last-ditch stuff before it still terminates.

    Yeah, the example was contrived because I tried to keep it terse yet demonstrate that telling the caller that the passed arguments cannot be right may be preferable to quietly doing something arbitrary. On a second thought, for that I could just have referred to std::vector::at() and operator[]. The “Save before termination?” idea came from editors that prompt “Save before quit?”, and it was meant to be something the caller might decide to do at a logic_error. For throwing let’s the caller decide what needs to be done; it’s his code that is at fault anyway.

  18. @Anton:
    > I still don’t understand why you believe returning error code in the exact same case (where assert would fail == bug == broken state) is fine while throwing an exception is not.

    I agree those are all the same, and I tried to say that with “Don’t use assertions to report run-time errors.” I’ll expand that to add: “Run-time errors should be reported using exceptions, error codes, or similar.”

    > The only argument you provided that has some sense is noexcept issue, but this is trivially to solve having continuation mode constant available from source code.

    No, see the bullet above that Guideline: “The program cannot recover programmatically by reporting a run-time error to the calling code, because by definition the program is in a state it was not designed to handle, so the calling code isn’t ready for that state.” Here I’ll also elaborate that by reporting a run-time error I mean “(via an exception, error code, or similar)”.

Comments are closed.