[HN Gopher] C++26: A User-Friednly assert() macro
       ___________________________________________________________________
        
       C++26: A User-Friednly assert() macro
        
       Author : jandeboevrie
       Score  : 57 points
       Date   : 2026-03-25 10:51 UTC (3 days ago)
        
 (HTM) web link (www.sandordargo.com)
 (TXT) w3m dump (www.sandordargo.com)
        
       | nyc_pizzadev wrote:
       | The nice thing about assert() is you can just define your own:
       | 
       | https://github.com/fiberfs/fiberfs/blob/7e79eaabbb180b0f1a79...
       | 
       | In this case, the ability to see the actual values that triggered
       | the assert is way more helpful.
        
         | BoingBoomTschak wrote:
         | Yeah, but the macro system being so pitiful makes me long for
         | one that allows something as magical as fiveam's is (https://gi
         | thub.com/lispci/fiveam/blob/e43d6c8e7da5a80d5c33e8...) instead
         | of having to write special cases for unary and binary
         | predicates.
        
       | omoikane wrote:
       | > (assert) doesn't follow the usual SCREAMING_SNAKE_CASE
       | convention we associate with macros
       | 
       | There are a few things like that, for example:
       | 
       | https://en.cppreference.com/w/c/numeric/math/isnan - isnan is an
       | implementation defined macro.
       | 
       | https://en.cppreference.com/w/c/io/fgetc - `getc` may be
       | implemented as a macro, but often it's a function.
        
         | nealabq wrote:
         | In C++ you should probably #include <cstdio> instead of
         | <stdio.h> unless you have a good reason. And especially avoid
         | #including both. <cstdio> provides the function std::getc(..)
         | while <stdio.h> usually provides getc(..) as a macro.
         | 
         | htons(..) and related socket-utility names are also often
         | macros, but I'm pretty sure there is not a std::htons(..) in
         | the C++ standard, partly because 'htons' is not an attractive
         | name. Since it's (sometimes) a macro don't qualify its
         | namespace like ::htons(..).
         | 
         | A long time ago in the Microsoft C (and later C++) dev envs
         | there were macros named "min" and "max", which I thought were
         | terrible names for macros.
        
           | adzm wrote:
           | > A long time ago in the Microsoft C (and later C++) dev envs
           | there were macros named "min" and "max", which I thought were
           | terrible names for macros.
           | 
           | Yeah, this is still in windows.h unless you #define NOMINMAX
           | 
           | I remember having to guard against this in some inline code
           | by surrounding the c++ calls with parenthesis, eg
           | `(std::min)(a, b)`
        
             | nananana9 wrote:
             | Yep. There's tons of others as as well. 16-bit x86 enjoyers
             | will be happy to know there are `near` and `far` macros
             | whose primary purpose in 2026 is to break my projection
             | matrices. And of course every Win32 function that takes
             | strings has a macro that resolves it to either the UTF-16
             | or ASCII variant, so your custom CreateWindow is now a
             | CreateWindowA, tough luck buddy.
             | 
             | I usually wrap Windows.h in a header followed by 100
             | #undefs to contain the disease.
        
       | MontagFTB wrote:
       | Putting code with side effects into an assert is asking for
       | trouble. Compile with NDEBUG set and the effects mysteriously
       | disappear! Anything beyond an equality expression or straight
       | boolean should be avoided.
        
         | nyc_pizzadev wrote:
         | This is just a symptom of a bad assert() implementation, which
         | funny enough is the standard. If you properly (void) it out,
         | side effects are maintained.
         | 
         | https://github.com/fiberfs/fiberfs/blob/7e79eaabbb180b0f1a79...
        
           | omoikane wrote:
           | assert() is meant to be compiled away if NDEBUG is defined,
           | otherwise it shouldn't be called assert(). Given that
           | assert() may be compiled away, it makes sense not to give it
           | anything that has side effects.
           | 
           | Abseil has the convention where instead of assert(), users
           | call "CHECK" for checks that are guaranteed to happen at run
           | time, or "DCHECK" for checks that will be compiled away when
           | NDEBUG is defined.
           | 
           | https://github.com/abseil/abseil-
           | cpp/blob/0093ac6cac892086a6...
           | 
           | https://github.com/abseil/abseil-
           | cpp/blob/0093ac6cac892086a6...
        
             | nmilo wrote:
             | If your assert compiles down to `if (condition) {}` in
             | production then the compiler will optimize away the
             | condition while keeping any side effects.
        
               | IshKebab wrote:
               | Yeah which may not be what you want. E.g.
               | `assert(expensive_to_compute() == 0)`.
               | 
               | The correct way to solve this is with debug asserts (as
               | in Rust, or how the parent described).
        
         | jmalicki wrote:
         | Side effects are bad of course, but anything beyond a straight
         | boolean or equality is bad?
         | 
         | `assert(vector.size() < 3)` is ridiculous to you?
        
         | nealabq wrote:
         | I don't mean to be that guy, but for "functional" programmers a
         | print statement has "side effects".
         | 
         | But your meaning is clear. In an assert expression, don't call
         | functions that might change the program/database state. Be as
         | "const" as possible.
        
           | toxik wrote:
           | Not just for functional programmers. Prints and other I/O
           | operations absolutely are side effects. That's not running
           | counter to the point being made. Print in an assert and
           | NDEBUG takes away that behavior.
        
             | nealabq wrote:
             | You're right of course. I was thinking specifically of
             | printing log/debug statements in the assert(..), but that
             | usually only happens if the assert(..) fails and exits, and
             | in that case the "no side effects" rule no longer matters.
        
         | usrnm wrote:
         | I once spent several days debugging that same mistake. Stuff
         | worked perfectly in tests but broke misteriously in production
         | builds. Couldn't stop laughing for a few minutes when I finally
         | figured it out.
        
         | maccard wrote:
         | Indeed.                  bool is_even(int* valPtr) {
         | assert(valPtr != nullptr);           return *valPtr % 2;
         | }
         | 
         | Does not do what you think it does with nullptr. A major game
         | engine [0] has a toggle to enable asserts in shipping builds,
         | mostly for this reason
         | 
         | [0] https://dev.epicgames.com/documentation/en-us/unreal-
         | engine/...
        
           | dccsillag wrote:
           | I'm sorry, but what exactly is the problem with the code?
           | I've been staring at it for quite a while now and still don't
           | see what is counterintuitive about it.
        
             | IshKebab wrote:
             | There's nothing wrong with it. It does exactly what you
             | think it does when passed null.
        
             | dataflow wrote:
             | Depends on where you're coming from, but some people would
             | expect it to enforce that the pointer is non-null, then
             | proceed. Which would actually give you a guaranteed crash
             | in case it is null. But that's not what it does in C++, and
             | I could see it not being entirely obvious.
        
               | IshKebab wrote:
               | Assert doesn't work like that in any language.
        
           | mhh__ wrote:
           | This is a very "Dr Dr it hurts when I do this" "Don't do
           | that" one it must be said.
        
           | secondcoming wrote:
           | Let's not vague post on HN. What's the problem with the
           | above?
        
         | samiv wrote:
         | That's why you define your own assert macro and keep in on
         | unconditionally. Your programs will be better for it.
        
           | jandrewrogers wrote:
           | An assertion can be arbitrarily expensive to evaluate. This
           | may be worth the cost in a debug build but not in a release
           | build. If all of assertions are cheap, they likely are not
           | checking nearly as much as they could or should.
        
             | samiv wrote:
             | Possibly but I've never seen it in practice that some
             | assert evaluation would be the first thing to optimize.
             | Anyway should that happen then consider removing just that
             | assert.
             | 
             | That being said being slow or fast is kinda moot point if
             | the program is not correct. So my advisor to leave always
             | all asserts in. Offensive programming.
        
         | andrepd wrote:
         | Rust has assert and debug_assert, which are self-explanatory.
         | But it also has an assert_unchecked, which is what other
         | languages incl C++ call an "assume" (meaning "this condition
         | not holding is undefined behaviour"), with the added bonus that
         | debug builds assert that the condition is true.
        
         | bluGill wrote:
         | Related our logging system has a debug which is not logged by
         | default but can be turned on if a problem in an area is found
         | (in addition to the normal error/info which is logged). I had
         | the idea that if a test fails we should print all these debugs
         | - easy enough to turn on but a number of tests failed because
         | of side effects that didn't show up when off.
         | 
         | i'm trying to think of how/if we can run tests with all logging
         | off to find the error and info logs with side effects.
        
       | throwpoaster wrote:
       | assert(spellcheck("Friednly"));
        
         | nananana9 wrote:
         | spellcheck.cpp:1:19: error: unexpected character <U+201C>
         | 1 | assert(spellcheck("Friednly"));             |
         | ^
        
       | amelius wrote:
       | Shouldn't the preprocessor be fixed, if it trips that easily on
       | common C++ constructs?
        
         | tom_ wrote:
         | I'm sure the standardization committee are always looking for
         | fresh ideas!
        
         | marginalia_nu wrote:
         | Preprocessor is just doing text transformations on the sources.
         | 
         | It's not really something that can be fixed, other than moving
         | away from the preprocessor and putting metaprogramming
         | capabilities into the language itself (which C++ has been
         | doing).
        
           | amelius wrote:
           | I mean, you could extend it such that a simple comma has no
           | special meaning.
           | 
           | But I agree, fewer special tricks is better and that includes
           | the preprocessor.
        
       | semiinfinitely wrote:
       | "C++47: Finally, a Standard Way to Split a String by Delimiter"
        
         | porise wrote:
         | I'm still waiting for C++ to support Unicode properly.
        
         | einpoklum wrote:
         | A standard way to split a string? Well, what's wrong with:
         | std::views::split(my_string, delimeter)          ?
        
           | nananana9 wrote:
           | Template bloat, terrible compile errors, terrible debug build
           | performance, 1 second of extra compile time per cpp file when
           | you include ranges, and you can't step through it in a
           | debugger.
        
       | grokcodec wrote:
       | Friedns shouldn't let Freidns post on HN without running spell
       | check
        
       | adzm wrote:
       | One of my favorite things from ATL/WTL was the _ASSERT_E macro
       | which additionally converts the source expression to text for a
       | better message to be logged
        
       | WalterBright wrote:
       | D just makes assert() part of the language:
       | 
       | https://dlang.org/spec/expression.html#assert_expressions
       | 
       | The behavior of it can be set with a compiler switch to one of:
       | 
       | 1. Immediately halting via execution of a special CPU instruction
       | 
       | 2. Aborting the program
       | 
       | 3. Calling the assert failure function in the corresponding C
       | runtime library
       | 
       | 4. Throwing the AssertError exception in the D runtime library
       | 
       | So there's no issue with parsing it. The compiler also
       | understands the semantics of assert(), and so things like
       | `assert(0)` can be recognized as being the end of the program.
        
       | wpollock wrote:
       | > assert(x > 0 && "x was not greater than zero");
       | 
       | Shouldn't that be "||" rather than "&&"? We want the message only
       | if the boolean expression is false.
        
         | puschkinfr wrote:
         | No, because the string will be implicitly converted to `true`
         | and `(a && true) == a` (for boolean `a`), so it will only be
         | `false` if the assertion fails. Using || would always evaluate
         | to `true`
        
           | wpollock wrote:
           | Thanks. However, I'm still not certain this will work as
           | intended. In C++ "&&" is short-circuiting; if the first part
           | of the expression is false then the second part is never
           | evaluated. So the result is never the message.
           | 
           | From <https://en.cppreference.com/w/cpp/language/operator_log
           | ical....>: For the built-in logical AND operator, the result
           | is true if both operands are true. Otherwise, the result is
           | false. This operator is short-circuiting: if the first
           | operand is false, the second operand is not evaluated.
        
       ___________________________________________________________________
       (page generated 2026-03-28 23:00 UTC)