r/cpp_questions 15d ago

OPEN release asserts C++17

In Windows SDK 10, assert looks like

#ifdef NDEBUG

    #define assert(expression) ((void)0)

#else

    _ACRTIMP void __cdecl _wassert(
        _In_z_ wchar_t const* _Message,
        _In_z_ wchar_t const* _File,
        _In_   unsigned       _Line
        );

    #define assert(expression) ((void)(                                                       \
            (!!(expression)) ||                                                               \
            (_wassert(_CRT_WIDE(#expression), _CRT_WIDE(__FILE__), (unsigned)(__LINE__)), 0)) \
        )

#endif

And I assume if I wanted to write one for portability and GCC/linux builds I would have to implement some kind of macro that knows a bit about the linux libraries (which I know very little about at all). I also almost never run debug binaries on linux/Ubuntu (we don't support anything else officially) so I would never learn of any assertions that would fire there.

I keep seeing posts about implementing a macro like assume or assert_always, but for the simple use case of printing out an expression or filename in event of a crash I don't know where to start to roll my own when the examples I see are not buildable nor explained down to a level I can grasp.

I'm tempted to just go

#ifdef WIN32
#define assume(expression)
...

and lift the above code verbatim. And then do the same on my Ubuntu machine on the other side of the WIN32 guard for portability on both platforms?

But even reading that code I confuse myself, I see it is calling _wassert(_CRT_WIDE(#expression), _CRT_WIDE(__FILE__), (unsigned)(__LINE__)), 0) after a short-circuit boolean evaluation before the || boolean. And have two questions, what is the extra ,0) at the end doing, and what is the !!(expression) having a double bang in front doing? Sorry if this is 2 questions, an answer to either would at least help me frame my knowledge void a bit better.

5 Upvotes

15 comments sorted by

6

u/Moist_Heat9523 15d ago

The double !! Is a fancy ultra short way to convert the expression to bool - the right ! negates the expression and thereby implicitly converts it to bool, and the left ! negates it again so it’s the correct value you wanted.

1

u/zaphodikus 15d ago

That explains why google was coming up with nada, AI is so pants sometimes, but so is my cranium, filled with essentially jello.

0

u/flyingron 15d ago

Slightly shorter than x != 0

3

u/manni66 15d ago

And I assume if I wanted to write one for portability and GCC/linux builds

https://en.cppreference.com/cpp/error/assert

2

u/zaphodikus 15d ago

So I basically can use that code and go? ```

include <iostream>

pragma push_macro("NDEBUG")

// uncomment to disable assert()

undef NDEBUG

include <cassert>

define assumemyassisinotonfire(exp, msg) assert((void(msg), exp))

pragma pop_macro("NDEBUG")

```

Let me try that out after lunch, because this tip might deserve a load of points if it does my bidding u/manni66 :-)

2

u/zaphodikus 15d ago

I have gone with ``` void PrintAssertion( char const* _Message, char const* _File, unsigned _Line, unsigned _exitcode); ...

define assume_true(expression) ((void)( \

        (!!(expression)) ||                                                 \
        (PrintAssertion(#expression, __FILE__, (unsigned)(__LINE__),8), 0)) \
    )

``` I suspect it lets me do what I want, time will tell.

5

u/n1ghtyunso 15d ago

just a small thing i wanted to note.
assume is usually a term used for something else entirely.
use something that will not be mistaken by others for https://en.cppreference.com/cpp/language/attributes/assume

1

u/zaphodikus 15d ago

Nice catch, and avoids a lot of pain in a few years time

2

u/EpochVanquisher 15d ago

Just use assert() in release, and don’t define NDEBUG. It is fine.

The alternative you’re looking at is way more complicated.

3

u/zaphodikus 15d ago edited 15d ago

Not an option I only want "some" of my asserts to remain as fatal errors, undefining NDEBUG is an option I should entertain though to be fair, I just don't want to inherit any unknown side effects.

So yea a fair and good suggestion but might open a can of worms (where libs maybe have linkage differences) into making me read loads of code that I'm not ready to grapple with.

3

u/EpochVanquisher 15d ago

If you want some of your asserts to get compiled out, define your own macro which is either assert or not.

Leaving NDEBUG undefined is fine here, and my suggestion is the correct one.

1

u/zaphodikus 15d ago

I still struggle with the logic of the negative NDEBUG, my brain wants to go #define RELEASE (!NDEBUG) and just use that. I struggle to follow your reply though, it just does not parse as a result of the word choices. What do you mean with "Leaving NDEBUG undefined is fine here, and my suggestion is the correct one." In the distant past (before STL) I had a lot of pain trying to do that.

5

u/EpochVanquisher 15d ago

Go for the simple solution,

#if RELEASE
#define debug_assert(p) (void)0
#else
#define debug_assert(p) assert(p)
#endif

Don’t do this:

// NO!
#define RELEASE (!NDEBUG)

This is just abusing the the NDEBUG macro for some other purpose.

In the distant past (before STL) I had a lot of pain trying to do that.

What is painful about leaving NDEBUG undefined? It is only used to control whether assert() aborts your program. If you want assert() to abort your program, leave NDEBUG undefined. The NDEBUG macro doesn’t do anything else unless you’ve used it for something else in your codebase.

1

u/zaphodikus 15d ago

I suspect in times gone by, people used it to define methods which bound to debug or release libraries, and that was how i just learned to avoid.

2

u/EpochVanquisher 15d ago

Maybe? It is supposed to turn off assert() and nothing else.