C++26: A User-Friednly assert() macro

(sandordargo.com)

62 points | by jandeboevrie 3 days ago

11 comments

  • WalterBright 5 hours ago

    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.

    • MontagFTB 9 hours ago

      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.

      • bluGill 4 hours ago

        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.

        • saagarjha 1 hour ago

          I actually feel like asserts ended up in the worst situation here. They let you do one line quick checks which get compiled out which makes them very tempting for those but also incredibly frustrating for more complex real checks you’d want to run in debug builds but not in release.

          • maccard 7 hours ago

            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/...

            • secondcoming 5 hours ago

              Let's not vague post on HN. What's the problem with the above?

              • dccsillag 7 hours ago

                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.

                • mhh__ 6 hours ago

                  This is a very "Dr Dr it hurts when I do this" "Don't do that" one it must be said.

                • usrnm 8 hours ago

                  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.

                  • samiv 7 hours ago

                    That's why you define your own assert macro and keep in on unconditionally. Your programs will be better for it.

                    • jandrewrogers 6 hours ago

                      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.

                    • nyc_pizzadev 9 hours ago

                      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...

                    • jmalicki 9 hours ago

                      Side effects are bad of course, but anything beyond a straight boolean or equality is bad?

                      `assert(vector.size() < 3)` is ridiculous to you?

                      • andrepd 4 hours ago

                        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.

                        • nealabq 8 hours ago

                          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 7 hours ago

                            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.

                        • nyc_pizzadev 10 hours ago

                          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.

                        • omoikane 9 hours ago

                          > (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 8 hours ago

                            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 6 hours ago

                              > 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)`

                          • grokcodec 8 hours ago

                            Friedns shouldn't let Freidns post on HN without running spell check

                            • adzm 6 hours ago

                              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

                              • wpollock 5 hours ago

                                > 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 4 hours ago

                                  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 2 hours ago

                                    You're right, thanks!

                                    This works too (but I wouldn't recommend it):

                                       assert( someBooleanExpression || ! "It is false" );
                                • amelius 8 hours ago

                                  Shouldn't the preprocessor be fixed, if it trips that easily on common C++ constructs?

                                  • marginalia_nu 8 hours ago

                                    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 7 hours ago

                                      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.

                                    • tom_ 8 hours ago

                                      I'm sure the standardization committee are always looking for fresh ideas!

                                    • semiinfinitely 8 hours ago

                                      "C++47: Finally, a Standard Way to Split a String by Delimiter"

                                      • porise 7 hours ago

                                        I'm still waiting for C++ to support Unicode properly.

                                        • einpoklum 6 hours ago

                                          A standard way to split a string? Well, what's wrong with:

                                              std::views::split(my_string, delimeter)
                                          
                                          ?
                                          • nananana9 6 hours ago

                                            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.

                                        • throwpoaster 8 hours ago

                                          assert(spellcheck(“Friednly”));

                                          • nananana9 6 hours ago

                                              spellcheck.cpp:1:19: error: unexpected character <U+201C>
                                                  1 | assert(spellcheck(“Friednly”));
                                                    |                   ^
                                            • throwpoaster 49 minutes ago

                                              Technically correct is best correct! Touché!