It really depends on the characteristics of what you want to achieve.
If you've never heard of Type Tunneling , for example, this might be a good time to read about it. There is a way to use pads to do crazy things ...
Otherwise, this is a simple version:
class AssertMessage { public: AssertMessage(): _out(nullptr) {} AssertMessage(std::ostream& out): _out(&out) {} AssertMessage(AssertMessage&& other): _out(other._out) { other._out = nullptr; } AssertMessage& operator=(AssertMessage&& other) { if (_out) { _out << "\n"; } _out = other._out; other._out = nullptr; return *this; } ~AssertMessage() { if (_out) { _out << "\n"; } } template <typename T> AssertMessage& operator<<(T const& t) { if (_out) { *_out << t; } } private: std::ostream* _out; };
Notice how, by nesting a pointer, we donβt need a global βnullβ object? This is the main difference between pointers and links. Also pay attention to the use of the assignment operator move / move to avoid outputting lines of 2 or more.
Then you can write the assert method:
AssertMessage UnitTest::assert(bool i) { return i ? AssertMessage() : AssertMessage(std::cerr); }
However .... I would seriously consider using a macro if you were you, because you get additional benefits:
#define UT_ASSERT(Cond_) \ assert(Cond_, #Cond_, __func__, __FILE__, __LINE__) AssertMessage assert(bool test, char const* condition, char const* func, char const* file, int line) { if (test) { return AssertMessage(); } return AssertMessage(std::cerr << "Failed assert at " << file << "#" << line << " in " << func << ": '" << condition << "', "); }
And then you get something like:
Failed assert at project/test.cpp
In large test suites, it is invaluable to have a file name and line number (at least).
Finally, the macro gets you even more: if you call a function in your message, for example ut.assert(x) << x.foo(); , then x.foo() needs to be evaluated completely, even if the message is not printed; it's pretty wasteful. However, with the macro:
#define UT_ASSERT(Cond_, Message_) \ while (!(Cond_)) { std::cerr << .... << Message_ << '\n'; break; }
then if the condition is true , the while body is not executed at all.