1

At certain points of my code I want to throw an exception and give it some information about the code where I've thrown it. Something like

void Foo() {
    if(condition)
        throw std::logic_error(ERROR_MSG);
}

// somewhere later in code
try {
    Foo()
} catch (std::exception & e) {
    e.what();
}

Now the question is – how do i construct this error message? I would like to give information about the file, the line of code, the function name and a bit of an own information. Something (I suspect) should be pretty standard usage – but after an extensive search I still didn't find something useful about this (as I thought) easy subject.

I just would like to write something like this

throw std::logic_arrow("Whoops! Error in (" +
                        __func__ + ") " + __FILE__ + ":" + __LINE__);

But this doesn't seem to work for various reasons. Here is what I achieved so far with my search:

  • There is already a similar question here, but this is about if one should use these macros, not how
  • This question deals with concatenating __FILE__ and __LINE__ in a macro, so I was a bit further to my answer
  • The __func__ macro seems to make difficulties, since "These identifiers are variables, not preprocessor macros, and may not be used to initialize char arrays or be concatenated with string literals" following to the gcc homepage. Well, it is now a variable I can use for instance in the printf function like here – but I didn't manage to master the transfer to my own problem
  • One guy was worried about a cleaner solution than Macros, using inline functions, so I could even improve a solution
  • C++14 techniques where suggested in another thread, using operator ""s
Ash
  • 4,611
  • 6
  • 27
  • 41
claudio
  • 139
  • 11
  • 2
    If you need a shorthand for throwing exception, you are probably using exception too much. – WiSaGaN May 12 '16 at 08:59
  • I doubt that would be useful. `__FILE__` and `__LINE__` point to where exception is thrown, which is irrelevant for user of your code. What is useful would be file and line where your function is called - but you can't get that information easily. If you need location where exception is thrown, simply use debugger. – el.pescado - нет войне May 12 '16 at 13:55
  • 1
    To illustrate, your exception would point to `throw std::logic_arrow(ERROR_MSG);` line, which for users of your function is useless. Better would be to point to `Foo()` line, but that's not trivial. – el.pescado - нет войне May 12 '16 at 14:01
  • Well, of course it would be best to provide information about the whole stack when I throw the exception, but we don't live in a perfect world. What I have now is at least the information where exactly my error happened. BTW I meant std::logic_error, dumb me. – claudio May 12 '16 at 14:09

4 Answers4

4

Just do this:

throw std::logic_error(std::string{} + "Whoops! Error in (" +
                    __func__ + ") " + __FILE__ + ":" + std::to_string(__LINE__));

live example

Sometimes you can format it like the IDE does its own error messages, then get double-click support.

throw std::logic_error(std::string{} + __FILE__ + "(" + std::to_string(__LINE__) + "): [" + __func__ +"] " + "Whoops! Error!  Whodathunk.");

or somesuch.

template<class E>
[[noreturn]] void fancy_throw( std::string msg,
  char const* file, char const* function,
  std::size_t line
) {
  throw E( std::string{} + file + "(" + std::to_string(line) + "): [" +
    function + "] " + msg );
}
#define EXCEPTION( TYPE, MESSAGE ) \
  fancy_throw<TYPE>( MESSAGE, __FILE__, __func__, __LINE__ )

and we get:

EXCEPTION( std::logic_error, "Whoops! Error!  Whodathunk." );

live example

I think this is poor. Instead, we can have a SOURCE_ERROR macro:

inline std::string make_source_error( std::string msg,
  char const* file, char const* function,
  std::size_t line
) {
  return std::string{} + file + "(" + std::to_string(line) + "): [" +
    function + "] " + msg;
}
#define SOURCE_ERROR(...) make_source_error(__VA_ARGS__, __FILE__, __func__, __LINE__ )

which places flow control outside of macros, but does the string building with the tokens inside the macro, and builds the string inside a normal function.

Only the things that must be done in a macro (or copy/pasted everywhere we use it) are done in a macro here. Which is how it should be.

live example

... and __VA_ARGS__ are there because the macro language doesn't understand some uses of , in C++.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Thank you, exactly what I was looking for. I posted a similar solution four minutes before you, but I didn't use a macro like your `SOURCE_ERROR`. I think I will use that although than I contradict my own comment in @JBL s answer. – claudio May 12 '16 at 14:02
2

I would suggest looking into Boost.Exception which does this and more (it's one of the most lightweight Boost libraries consisting only of a few headers).

Tamás Szelei
  • 23,169
  • 18
  • 105
  • 180
  • Thank you for your answer, but it does **not** work, at least not in g++ version 6.1.1. It complains about being "`unable to find string literal operator 'operator""__func__'`" and also an "`stray '#' in program`". I tried to insert whitespace as well to remove the '#' but neither did help. – claudio May 12 '16 at 12:28
  • @claudio sorry for wasting your time, you are right. The issue is that __func__ does not expand to a raw literal but a variable. Also, __LINE__ needs to be stringified with a delayed expansion. I'm removing most of the answer, but I think the Boost.Exception suggestion is still relevant. – Tamás Szelei May 12 '16 at 12:42
1

I've been doing that by using a couple of macros (so that it would reduce the hassle of typing the whole thing each time, plus it adds consistency), and then creating temporary std::strings to concatenate them easily. There might be a simpler way, but it works well and you only type it once.

// Helper macros to convert a macro string constant to an actual string in another macro.
#define STRINGIFY2(x) #x
#define STRINGIFY(x) STRINGIFY2(x)

#define CURRENT_LOCATION std::string("At "STRINGIFY(__FILE__)":") + __func__ + std::string("(" STRINGIFY(__LINE__) "): ")

You can then use it and add some info afterwards that way:

throw std::logic_error(CURRENT_LOCATION + "Description here");

Watch it live here

JBL
  • 12,588
  • 4
  • 53
  • 84
  • Thank you for your answer, but that is exactly what was written in [second item's link](http://stackoverflow.com/questions/19343205/c-concatenating-file-and-line-macros) and does **not** solve the problem with `__func__`. – claudio May 12 '16 at 12:10
  • @claudio Sorry, I missed that, but then I don't really get your issue with `__func__`. It's indeed not a macro, but an implicitly declared variable in each function. Edit your question with the actual issue you face with it. In my example, it works, so I don't get the "it does not solve the problem with `__func__`". You might be trying to do something different or facing an issue I don't have. What happened when you "tried to transfer to your own problem"? – JBL May 12 '16 at 12:29
  • Well, the issue is that I – as explained in the second item – cannot use it to concatenate string literals. I tried (before I knew that) to use it is input for strcat() and strcpy() but both resulted in segfaults. I tried different versions of the macro you suggested resulting in compiler errors. I still think it could work, because in [this](http://stackoverflow.com/questions/27295881/c-concatinating-func-and-const-char-literal) they used it as input for printf() but I do not know how to define an appropriate function/macro. – claudio May 12 '16 at 12:37
  • 2
    Alright.. And what is the issue with what I do? Basically, in my solution it works because I concatenate an `std::string` with `__func__`, which is a `const char[]`. And `operator+()` is defined for `std::string` and this type in that order. Is it the use of temporary `std::string`? The link you posted show a use of `printf` with `__func__` which is perfectly fine. You can use a `const char[]` as an input for a `printf` format with `%s`. `__func__` is not a string literal, so you should not try to use it as one. – JBL May 12 '16 at 13:05
0

Okay, after some trying I got an "easy" solution which doesn't even need the use of macros, only an inline function. It does compile with g++ 6.1.1 and also "work" es expected (throwing the exception).

#include <iostream>
#include <stdexcept>
inline const std::string
Exception_helper(   const std::string msg,
                    const char * const func_name,
                    const char * const file_name,
                    const std::size_t line_number) {
    return msg + " " + func_name + "() in " + file_name + ":" +
        std::to_string(line_number);
}

void Foo() {
    throw std::logic_error(Exception_helper("Whoops! Error in",
                                            __func__,
                                            __FILE__,
                                            __LINE__));
}

int main() {
    try { Foo(); }
    catch (std::exception & e) {
        std::cerr << e.what() << std::endl;
        exit(1); }
    return 0;
}

I could try to even implement Exception_helper as constexpr function, but no time for that at the moment.

Again, thanks to all who helped me find an answer. Stackoverflow is the best.

claudio
  • 139
  • 11
  • So you will use a function that forces you to type the same last three parameters each time you call it? – JBL May 12 '16 at 13:23
  • Yes, I have to, because the last three parameters provide the information about where exactly I've thrown the exception. There is – as far as I know or I can see – no possibility to avoid that (except to use a macro in that place, but that is against my taste or even good style). If I had stored these parameters inside the function body to avoid the last three parameters, the error message would give me every time the place of Exception_helper which is definitely **not** what I want. – claudio May 12 '16 at 13:53
  • @Yakk used in his solution an macro to avoid the three parameters and I think I will use that – claudio May 12 '16 at 14:12
  • I do know that (otherwise I wouldn't use a macro, yet alone the `STRINGIFY`) but that's actually exactly the same concept behind what I proposed. So I'm kinda surprised. Have a good day. :) – JBL May 12 '16 at 14:21