1

I am trying to make a print function which prepends the print statement with file, function, and line number.

The macro debug("test %d", 1) should print:

[src/main.cpp:main():8] test 1

This answer claims to do what I want, and almost does it, but doesn't work as-is. (And the question is not the same as this one) Using that answer, I was able to come up with this macro:

#include <cstdio>  //needed for printf
#define debug(a, args...) printf("[%s:%s():%d] " #a "\n", __FILE__, __func__ , __LINE__, ##args)

This is extremely close to what I want, but adds quotes around the string, like below. How can I print without the quotes?

[src/main.cpp:main():8] "test 1"

2 Answers2

1

There are 2 problems with your macro definition:

  1. It uses incorrect syntax
  2. The name of a variable may be passed as a; in this case you cannot simply put the variable next to 2 string literals to do a concatenation.

The latter problem can be solved by using a string and the + operator or simply using multiple printfs.

If C++20 __VA_OPT__ is available, the following solution could be used.

#include <cstdio>
#include <utility>

namespace Logging
{
    template<typename ...Args>
    inline void Debug(size_t lineNumber, char const* filename, char const* functionName, char const* format, Args&&... args)
    {
        std::printf("[%s:%s():%zu] ", filename, functionName, lineNumber);
        std::printf(format, std::forward<Args>(args)...);
        std::printf("\n");
    }
}

#define debug(format, ...) ::Logging::Debug(__LINE__, __FILE__, __func__, format __VA_OPT__(,) __VA_ARGS__)

Pre C++ 20 you could create a helper object to deal with the case of a sue of the macro with just a single parameter

#include <cstdio>
#include <utility>

namespace Logging
{
    class Logger
    {
    public:
        Logger(size_t lineNumber, char const* filename, char const* functionName, char const* format)
            : m_lineNumber(lineNumber), m_filename(filename), m_functionName(functionName), m_format(format)
        {}

        template<class ...Args>
        inline void Debug(Args&&... args)
        {
            std::printf("[%s:%s():%zu] ", m_filename, m_functionName, m_lineNumber);
            std::printf(m_format, std::forward<Args>(args)...);
            std::printf("\n");
        }
    private:
        size_t m_lineNumber;
        char const* m_filename;
        char const* m_functionName;
        char const* m_format;
    };
}

#define debug(format, ...) ::Logging::Logger(__LINE__, __FILE__, __func__, format).Debug(__VA_ARGS__)
fabian
  • 80,457
  • 12
  • 86
  • 114
  • Pre-C++20 the comma before the variadic parameter was mandatory (so `debug("foo",)` is legal only with the comma). Could probably solve this by combining `format` and `...` into just `...`. – HolyBlackCat Nov 20 '21 at 21:28
  • Is there a speed or safety reason for doing this using a namespace the way you've done it, compared to just making a multi-line define? I was able to modify my define to use three printf statements the way you do in this answer, and it works exactly the way I wanted. – progressiveCavemen Nov 22 '21 at 16:41
  • 1
    @progressiveCavemen It's personal preference. you could of couse create a define `#define debug(format) do { ... } while(false)` to make the macro behave as if it was a single function call, but using a inline function you shouldn't loose anything and it's imho best to keep a macro as short as possible, since macros are usually harder to work with than functions. (E.g. try setting a breakpoint to a specific statement of a macro...) – fabian Nov 22 '21 at 17:21
0

Replace #a with just a:

#define debug(a, args...) printf("[%s:%s():%d] " a "\n", __FILE__, __func__, __LINE__, ##args)

A single # followed by a macro parameter stringifies the parameter. IOW, it takes whatever the caller passes into the parameter and makes a string literal out of it. For example:

#define macro(a) #a

macro(1) would be "1", macro(x+y) would be "x+y", etc.

If the parameter contains quote characters on input, they will be stored as actual characters within the string literal, which is not what you want in this situation.

Your parameter is being passed a string literal that you want to use as-is, so simple macro replacement will suffice, so you need to drop the #. You want the compiler to expand debug("test %d", 1) into:

printf("[%s:%s():%d] " "test %d" "\n", __FILE__, __func__ , __LINE__, 1)

So it can then merge adjacent string literals together into:

printf("[%s:%s():%d] test %d\n", __FILE__, __func__ , __LINE__, 1)

You do not want the compiler expanding debug("test %d", 1) into:

printf("[%s:%s():%d] " "\"test %d\"" "\n", __FILE__, __func__ , __LINE__, 1)

And thus merge the adjacent string literals into:

printf("[%s:%s():%d] \"test %d\"\n", __FILE__, __func__ , __LINE__, 1)

Which is exactly what you were seeing happen when your original code used #a.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Note that this will only work if the first parameter to `debug` is an actual string literal. If it is instead a variable (trying to do a variable format) it will give a syntax error. This may actually be desired behavior. – Chris Dodd Nov 20 '21 at 21:31
  • 1
    @ChrisDodd true. But to log just a variable by itself, eg instead of `int var; debug(var)`, I would use it as a format parameter instead: `int var; debug("%d", var)` – Remy Lebeau Nov 20 '21 at 21:39