1

In my code I use printf-like LOG(const char* fmt, ...) macro. Under the hood it prints/logs to different output destinations (file, console, debugger output). It's my own macro, so it does printf format when there are 2+ args, and logs strings as-is when only one argument is passed.

I want to update or extend it, so that it could take list args to log. So that it could work similar to console.log in javascript, except it would also log arg names. For example:

int64_t x = -1234;
unsigned volume = 10;
double pi = 3.1415926535;
const char* name = "test";
std::string str = "hello";

LOG(x, volume, pi, name, str);

should log something like that:

x:-1234, volume:10, pi:3.1415926535, name:test, str:hello

Are there some examples/libraries that do something like that? Note, I'm not limited on what c++ version to use (I use latest), but I will not use iostreams for sure.

2
  • This #define reference could be helpful to read. Pay close attention to the # and ## operators inside macro bodies. Commented May 17 at 13:32
  • This questions should have many answers, the correct ones will use variadic templates. But also, NEVER assume you can mimmick exactly what you know from one language (javascript) into another language (C++). In the end I think you should even decouple logging from your functional code alltogether (have a look at dependency injection) Commented May 17 at 13:40

3 Answers 3

2

Main issue is on how to do for-each macro application primitive on variadic arguments. Here I'm going to use a method described here: https://www.scs.stanford.edu/~dm/blog/va-opt.html verbatim.

// Format single argument
#define LOG_1(x) #x ":" << x
#define LOG_2(x) << ", " << LOG_1(x)
#define LOG(var, ...) do { \
    std::cout << LOG_1(var) FOR_EACH(LOG_2 __VA_OPT__(,) __VA_ARGS__) << std::endl;  \
 } while(false)

See https://godbolt.org/z/eEnhd49h3 for a full example.

If you want std::format-based approach, that can be done fairly simply as well (arguably even simpler):

#define LOG(x) std::cout << x << std::endl
#define LOG_1(x) #x ":{}"
#define LOG_2(x) ", " LOG_1(x)
#define LOG_FORMAT(var, ...) LOG_1(var) FOR_EACH(LOG_2 __VA_OPT__(,) __VA_ARGS__)
#define LOG_ARGS(...) do { \
    LOG(std::format(LOG_FORMAT(__VA_ARGS__), __VA_ARGS__));  \
 } while(false)

https://godbolt.org/z/nb6YaGoch

Or, since you tagged Boost Preprocessor:

#define LOG_1(x) #x ":" << x
#define LOG_2(d, state, x) state ", " LOG_1(x) << 
#define LOG(var, ...) do { \
    std::cout << BOOST_PP_LIST_FOLD_LEFT(LOG_2, LOG_1(var) <<, BOOST_PP_VARIADIC_TO_LIST(__VA_ARGS__)) std::endl;  \
 } while(false)

Both of these need C++20 to handle single-argument case though (__VA_OPT__ support is needed).

Sign up to request clarification or add additional context in comments.

9 Comments

sorry I updated too late perhaps, I don't use iosrteams. printf is ok, std::format is also ok (I don't use it yet, but will start using it most likely). Check my approach I added.
Doesn't really matter, main thing is to find a way to apply a macro to each item in variadic arguments (which Boost PP gives you as you found out for yourself). Once you have that, you can apply whatever transformation needed to adapt to your needs. printf isn't really an option because you can't get a type of each element for its format string; std::format works just fine. I've edited my answer to add std::format based approach.
my solution works with ms compiler only for some reason. It looks like std::format(std::vector) doesn't compiler with clang/gcc, but works with ms compiler. However, it works with your solution. No idea why.
ok, I see now. It's c++23 to print vectors.
@PavelP It's not that much of a code tbh. However you might want to try the great fmt library (fmt.dev). It's a drop-in replacement, doesn't need C++23 for ranges, and call-site code is fairly tiny.
|
2

You can do something like this:

For type of variable you can add typeid(myint).name()

Next step you should deal with variadic function implementation to get arguments, parse types and forward to printf. The main difficulty will be with very big number of types, including user-defined. I don't know how to prepare format string for printf out of this data.

Comments

1

With modern c++ and boost::preprocessor, I made it working surprisingly quickly:

#include <boost/preprocessor.hpp>
#include <format>

#define LOGARGS_PAIR(r, data, elem) (", ")(BOOST_PP_STRINGIZE(elem) ":{}")
#define LOGARGS(...) LOG(std::format(BOOST_PP_SEQ_CAT(BOOST_PP_SEQ_POP_FRONT(BOOST_PP_SEQ_FOR_EACH(LOGARGS_PAIR, ~, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)))), __VA_ARGS__))

Even works with vectors:

int64_t x = -1234;
unsigned volume = 10;
double pi = 3.1415926535;
const char* name = "test";
std::string str = "hello";
auto coords = std::vector{15, 8, 128};

LOGARGS(x, volume, pi, name, str, coords);

prints this:

x:-1234, volume:10, pi:3.1415926535, name:test, str:hello, coords:[15, 8, 128]

Note, that it all expands to my own LOG macro that works as puts call with a single std::string_view argument.

The problem with this solution is that it uses boost::preprocessor and I assume this thing slows down compilation (I haven't verified it). It also uses std::format, that's why vectors and some std containers could be printed without any work.

Overall, it takes variadic args, converts it to sequence, then for each element it converts them using LOGARGS_PAIR to the following seqeuence:

LOGARGS_PAIR(x) LOGARGS_PAIR(volume) ...

LOGARGS_PAIR converts to:

(", ")("x" ":{}") (", ")("volume" ":{}") ...

Then, I remove first element from the sequence using BOOST_PP_SEQ_POP_FRONT and flatten it using BOOST_PP_SEQ_CAT. This creates format string for std::format:

"x" ":{}" ", " "volume" ":{}" ...

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.