I don't know if this is a common recommendation for all C++ code in general, but at least in some contexts it is recommended to not use the assert
macro and instead throw exceptions.
I've always had a problem with this approach. How do I know which line triggered the exception?
Well, yes, we have the __LINE__
preprocessor constant. However, passing it is non-trivial.
Approaches I tried:
#include <stdexcept>
int main() {
throw std::logic_error("__LINE__");
}
terminate called after throwing an instance of 'std::logic_error'
what(): __LINE__
Aborted (core dumped)
Well, that's not precisely what I wanted. Let's try again:
#include <stdexcept>
int main() {
throw std::logic_error(__LINE__);
}
wtf.cc: In function ‘int main()’:
wtf.cc:4:34: error: invalid conversion from ‘int’ to ‘const char*’ [-fpermissive]
throw std::logic_error(__LINE__);
^
In file included from wtf.cc:1:0:
/usr/include/c++/7/stdexcept:124:5: note: initializing argument 1 of ‘std::logic_error::logic_error(const char*)’
logic_error(const char*) _GLIBCXX_TXN_SAFE;
^~~~~~~~~~~
Duh. What did I want? OK, let's try again, this time correctly:
#include <stdexcept>
#include <sstream>
std::ostringstream lineno;
int main() {
throw std::logic_error((lineno << __LINE__, lineno.str()));
}
terminate called after throwing an instance of 'std::logic_error'
what(): 7
Aborted (core dumped)
This finally works, however, copy-pasting all of this each time I want to have an assert in my code is already tedious and annoying; if I'll ever want to include the filename as well this will only get worse.
However, the typical ways to remove code duplication will clearly fail here:
#include <stdexcept>
#include <sstream>
void fatal() {
std::ostringstream slineno;
slineno << __LINE__;
std::string lineno = slineno.str();
throw std::logic_error(lineno);
}
int main() {
fatal();
}
terminate called after throwing an instance of 'std::logic_error'
what(): 6
Aborted (core dumped)
Not this precise line number, sadly.
And finally, the best I could come with:
#include <stdexcept>
#include <sstream>
#define FATAL {std::ostringstream slineno; \
slineno << __LINE__; \
std::string lineno = slineno.str(); \
throw std::logic_error(lineno);}
int main() {
FATAL;
}
terminate called after throwing an instance of 'std::logic_error'
what(): 10
Aborted (core dumped)
Is this the correct approach? My doubts stem from the fact that (a) I hear macros in C++ are recommended against; (b) If this was correct I suppose people would have to reinvent this over and over; I mean this is such a simple utility this would have to be in the standard library, right? So either I missed something from the standard library or I'm Doing Things Wrong™, I suppose.
How to do this correctly?