3

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?

1 Answers1

5

I hear macros in C++ are recommended against;

Yes, but that doesn't mean to never use them. Right now we don't have anything better than __LINE__ as a non-macro solution, so I really don't see a problem using a macro for this.

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?

It is: In the form of assert. First of all, std::logic_error is a pretty bad exception, since logic errors are programming errors and can't be handled by any exception handling code in general.

Throwing std::logic_error when you have a assertion is really bad style, since some code can catch it and then the program silently continues, which isn't really the point of an assertion.

assert is not bad style; in fact, we are getting contracts in C++20 where we will have a non-macro assert and pre/post conditions. :) To further drive the point: LLVM is full of asserts and it's not a bad codebase by far.

Rakete1111
  • 47,013
  • 16
  • 123
  • 162