Search code examples
c++macrosvariadic-macrosfmt

How to perform different behaviors based on the length of variadic args in cpp macro?


I'm trying to make a short-hand for logging (log4cplus) + libfmt. I have defined a macro in my util header:

#define T_TRACE(fs, ...) \
    LOG4CPLUS_TRACE(Logger::Instance().logger, fmt::format(fs, __VA_ARGS__).c_str())

Then, every other cpp just include this header, invoke T_TRACE to perform logging. e.g.
T_TRACE("Version is {}", "1.0.0");
Everything works fine until I want to log a string contains {} pair (a simple json string).
T_TRACE("{\"name\": \"value\"}"), the fmt lib will treat it as a formatter, so I need to inform the macro that insert a default placeholder "{}" for me if there is only one argument.

The reason for using macro is that I also want to log the source file and lines. If I wrap it in a function, all the file/line info would be in that wrapper function.


Solution

  • Finally, I come up with a solution based on Kramer's answer.
    Unamed logging: T_TRACE(config) => FMT_SERIALIZER_RESULT

    #define T_TRACE(o, ...) \
        LOG4CPLUS_TRACE(Logger::Instance().logger, LogImpl(o, __VA_ARGS__).c_str())
    
    template <typename T, typename... args_t>
    inline auto LogImpl(const T& obj, args_t&&... args)
    {
        if constexpr (sizeof...(args_t))
            return fmt::format(obj, args...);
        else
            return fmt::format("{}", obj);
    }
    

    Named logging: T_TRACE(config) => config: FMT_SERIALIZER_RESULT

    #define T_TRACE(o, ...) \
        LOG4CPLUS_TRACE(Logger::Instance().logger, LogImpl(#o, o, __VA_ARGS__).c_str())
    
    template <typename T, typename... args_t>
    inline auto LogImpl(const char* name, const T& obj, args_t&&... args)
    {
        if constexpr (sizeof...(args_t))
            return fmt::format(obj, args...);
        else
            return fmt::format("{}: {}", name, obj);
    }