Smart assert library

Good day! :3

There is this beautiful tests library:
https://github.com/catchorg/Catch2
It implements some dark magic and can parse REQUIRE statement:

1
2
3
4
TEST_CASE("Test name") {
    int k = 0;
    REQUIRE(k == 42);
}


and print result:

1
2
3
4
main.cpp:12: FAILED:
  REQUIRE( k == 42 )
with expansion:
  0 == 42


Yes, it can not parse complicated statements, like

 
REQUIRE(k == 42 || (k > 12 && !my_vector.empty()));


But my question is not about tests at all. It is about assert:
Is there some library that can do similar magic for assert?
Standard assert from <cassert> does not show values, just the line where assert failed.
There are some other asserts that show the values, but require more complex syntax:

 
some_assert_eq(k, 42);

 
some_assert(some_enum::EQ, k, 42);

 
some_assert(k == 42)(k)(42);

...or something like that.
But I am interested in assert that can show values and have simple syntax.
Last edited on
I don't know a library but you can rig a try catch block that prints the offending values.

this kind of work is the one place where macros are your friend.
a macro can print the text it gets, so you could define a macro that prints what it gets (failed k==42|| k>12 && something) and also evaluates it (expects boolean that evals true (no print) or false (prints) so whatever is passed in is just resolved to a single true/false...

try this:
#define req(msg,cond) if(!(cond)) cout << msg <<" " << #cond<<'\n';

with your test code:
req("failed requirement", (k == 42 || (k > 12 && !my_vector.empty()))); //if I got the () right.

if it failed, it would print
failed requirement (k == 42 || (k > 12 && !my_vector.empty()))

you can add the line and file and such macros to see where it happened and make it smarter.
you can change if to assert, but you need to print and flush the text first if you do that.
to get it out of release, you can wrap it in a #ifdef debug and #else to turn it into a do-nothing for release builds.
Last edited on
I don't think a try-catch block is going to give you access to the value, although jonnin is on the right track with a #define.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>

#define MYASSERT(cond)\
	if (! (cond))\
	{	std::cerr << "MYASSERT failed: "\
		<< #cond << " AT "\
		<< __FILE__ << " line " << __LINE__ << std::endl;\
                exit(1);  //  or possibly a throw
	}

int main()
{
	int var = 42;
	MYASSERT(var != 42);
}


MYASSERT failed: var != 42 AT C:\Users\source\repos\junk1.cpp line 13

Last edited on
it wouldn't. but if you use the message idea, you can put the condition text in there. Tedious, but some people/places really, really hate macros. I have a dim view of them but for profiling (if you don't have one in your tools) or debugging (as above) and one or two other things they are pure gold.
@jonnin, @AbstractionAnon
Thank you for the suggestions! :3
I tried some things and got nothing better than default assert from <cassert>, which generously prints the statement string, e.g. "k == 42", the number of line and filename.

But it is not enough.
When I want k to be equal to 42, and later it fails, it is not enough to know the value of (k == 42) statement, which would be false, obviously. In ~90% of cases I want to know what was the actual value of (k). Because when it is 43 and when it is 100500 and when it is -1, that can right away lead to completely different sources of mistake. At the moment I do that with additional actions on the second run, like breakpoint or print or something. And I noticed, that I do that a lot just to figure out what was the actual value of k, which logically can be avoided if assert(k == 42) already written there.

In Catch2 implementation it is heavy dark magic, of course based on define macros, a lot of them, not a couple of lines. And I do not have such skills with define to do that, so decided to look for a library.
Last edited on
Good luck with your search. If you can't find the library, an extra parameter will do that:

#define req(msg,cond) if(!(cond)) cout << msg <<" " << #cond<<'\n';
becomes
#define req(msg,cond, val) if(!(cond)) cout << msg <<" " << #cond<<" variable = " <<val << '\n';
or whatever. You can pile on all kinds of info, just makes the interface to it bigger, and of course the variable has to be supported by cout, which many things are not (eg vector).

these are mostly going to look like c++ functions. the only things you are really using that force it to be a macro are
- the # operator, which converts arguments to text, eg variable names
- the line and file macros, which are simply not available any other way
- type ignorance. c++ is strongly typed, but macros ignore this, allowing you to take a vector or a string or a double or a class object etc all into the same macro (for better or worse: not everything will work as expected if you are not careful).
and the \ operator which simply allows multi-line macros.

every place I have coded has one of these, but they are often home-grown and not public. Its really a TMI problem... there are too many of them out there and each one is close but never exactly what you want, so you always end up making your own.
Last edited on
But it is not enough. ... I want to know what was the actual value of (k).

As catch2 is open source, have you considered looking at the source?

Step thru it in the debugger, and that will act as a guide as to where to concentrate your search.
@Satoshi Yoda - You shouldn't be afraid of macros. IMO, they get a bad rap because they're from the C way of doing things. You can get what you want with token pasting. In the traditional assert macro, condition is a single argument, which is why you don't get the value of k. In the following, we assume the condition is equal so we can see arg1 and arg2 individually. You should be able to see how to write macros for other conditions if you need them.
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
using namespace std;

#define assertEQ(v1,v2)	if(! (v1==v2))\
    {	cerr << "assertEQ failed " << #v1 << " (" << v1 << ") == (" << v2 << ") " << #v2 << endl;\
        cerr << " at line " << __LINE__ << " in " << __FILE__ << endl;\
    }
	
int main()
{
	int k = 0;
	assertEQ(k, 42);
}



assertEQ failed k (0) == (42) 42
 at line 12 in C:\Users\source\junk1\junk1\myassert.cpp



Last edited on
> they get a bad rap because they're from the C way of doing things.

They have a bad reputation because
a. they do not obey the scope and type rules of the language
b. they do not obey the C++ rules for argument passing;
because they pre-empt the syntactic and semantic rules of the language.

For instance, with the above macro:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
using namespace std;

#define assertEQ(v1,v2)	if(! (v1==v2))\
    {	cerr << "assertEQ failed " << #v1 << " (" << v1 << ") == (" << v2 << ") " << #v2 << endl;\
        cerr << " at line " << __LINE__ << " in " << __FILE__ << endl;\
    }

template < typename A, typename B > struct X { static constexpr std::size_t SZ = sizeof(A) + sizeof(B) ; } ;

int main()
{
	int k = 0;
	assertEQ( ++k, 42 )
	std::cout << "k == " << k << " (it has been incremented twice).\n" ;

	// assertEQ( X<int,double>::SZ, X<double,int>::SZ ) // *** error: too many arguments
	assertEQ( (X<int,double>::SZ), (X<double,int>::SZ) ) // this ugly construct is actually required
}


CoreGuidelines: "Scream when you see a macro that isn’t just used for source control (e.g., #ifdef)"
https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-macros

Boost preprocessor library: "As a rule of thumb, if you can find a clean and manageable way to do something without the preprocessor, then you should do it that way."
https://www.boost.org/doc/libs/1_77_0/libs/preprocessor/doc/topics/problems.html
Last edited on
CoreGuidelines: "Scream when you see a macro that isn’t just used for source control (e.g., #ifdef)"

too harsh. I think they are fine for this job (debugging/crash info dumping).
the boost idea is more correct to me... it gets the message across, and its a rule of thumb, which you can break with discretion.

outside of standard preprocessor (#include, #ifdef, etc) seen in nearly every header file (including magic for cross platform fixes eg ifdef windows include windows.h etc) my list of where allowed is short enough:
1) debugging, as this thread
2) force inline (very, very rare use case: abuse of #include )
3) home-rolled profiling (sometimes you need more than your tool offers, or lack the tool at all)
4) legacy code maintenance (eg win32 resource file)

can't think of too many more.
Last edited on
Topic archived. No new replies allowed.