1

I want to write logger which would print logs to console using printf function.

Let's assume that I have following piece of code:

class Logger {
public:
    Logger(std::string header = "") : header_(header) {}
    template<class ...Args>
    void LogInfo(const char* message, Args... args);
private:
    std::string header_;
};

template<class ...Args>
void Logger::LogInfo(const char* message, Args... args) {
    printf(message, args...);
}

This logs well but problem is when I call:

const char* s = "Monty Python";
Logger logger("[Header]");
logger.LogInfo("%d", s);

logger prints pointer-value without any warning, while printf call causes error (with my compilation flags)

error: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘const char*’ [-Werror=format=]
 printf("%d", s);

The point of whole code is that I want to get this error during LogInfo call. How can I do such a thing?

  • Does is not possible to do. `printf` is parsed at runtime. Have a look at https://stackoverflow.com/questions/20424625/werror-format-how-can-the-compiler-know/20424867#20424867 – ConsistentProgrammer Jul 17 '18 at 12:16
  • 3
    I'm not sure it's possible with variadic templates, but you could try the `format` [common function attribute](https://gcc.gnu.org/onlinedocs/gcc-8.1.0/gcc/Common-Function-Attributes.html#Common-Function-Attributes) (and remember that member functions have a hidden first argument for the `this` pointer, which means the first argument in your list is really the *second* argument for the `format` attribute). – Some programmer dude Jul 17 '18 at 12:19
  • But if you want a proper type-safe "printf" variant (yours still isn't) then consider using libraries such as [Boost format](https://www.boost.org/doc/libs/1_67_0/libs/format/doc/format.html). – Some programmer dude Jul 17 '18 at 12:25
  • I will try with an attribute but very confusing for me is that standalone printf causes the error but wrapped does not. – Patryk Kaczmarek Jul 17 '18 at 12:38
  • Why do you want to use the printf format? Use the streaming operator and enjoy type safety. – UKMonkey Jul 17 '18 at 12:40
  • @UKMonkey problem is that I have similar function as printf, my function also causes Werror-format but, again also, it does not cause this err while wrapped in class member function. Maybe I should mention that printf is only example – Patryk Kaczmarek Jul 17 '18 at 12:48
  • @PatrykKaczmarek It is because `attribute` format is supposed to be used with variadic functions, not with variadic tempaltes. The third parameter is supposed to be index of the first variadic argument while variadic template function does not have any variadic arguments, each template instantiation will have a well-defined fixed amount of arguments. You should covert this variadic template function into a non-template variadic function. – user7860670 Jul 17 '18 at 12:50
  • @Tyker attribute format has been already mentioned and explained why it won't work in case of variadic template. – user7860670 Jul 17 '18 at 12:52
  • @PatrykKaczmarek I understand your problem - but it's a problem you have made for yourself by not using `operator <<` - so I'm asking why don't you want to use that? – UKMonkey Jul 17 '18 at 13:04
  • @UKMonkey because I have to use this similar-printf-func. This is moreover 'how to deal with given code' example I think. – Patryk Kaczmarek Jul 17 '18 at 13:23

3 Answers3

1

Falling back to printf function family and format strings brings you back quite a lot of issues concerning type safety You might be better off with modern C++ streaming mechanism. Sure, one wouldn't really want to log like this:

logger << "first: " << x << "second: " << y << commit;

So coming up with some alternative approach avoiding the problem with need for appropriate format string parameters; idea is the following:

  • arguments are inserted one after another at designated locations in the format string
  • insertion locations are identified by the character pattern %#
  • pattern %## suppresses argument insertion and is replaced with the insertion pattern as string

Disadvantage: we have to do the parsing ourselves:

void logInfo(char const* message)
{
    char const* m = message;
    while((m = strchr(m, '%')))
    {
        if(*++m == '#')
        {
            if(*++m != '#')
            {
                std::cout.write(message, m - message - 2);
                std::cout << "<missing argument>";
            }
            else
            {
                std::cout.write(message, m - message);
                ++m;
            }
            message = m;
        }
    }
    std::cout << message << std::endl;
}

template<typename A, typename ... AA>
void logInfo(char const* message, A a, AA ... aa)
{
    char const* m = message;
    while((m = strchr(m, '%')))
    {
        if(*++m == '#')
        {
            if(*++m != '#')
            {
                std::cout.write(message, m - message - 2);
                std::cout << a;
                return logInfo(m, aa...);
            }
            std::cout.write(message, m - message);
            message = ++m;
        }
    }
    std::cout << message << std::endl;
}

Sure, there is quite some common code yet, leaving to you to optimise, it's just for the idea...

Worked fine with the following examples:

logInfo("t1");
logInfo("t2", 7);
logInfo("t3: %#", 12);
logInfo("t4: %#%##", 10);
logInfo("t5: %#%%#", 12);
logInfo("t6: %#% baz", 10);
logInfo("t7 1: %# 2: %# 3: %#", 10, 12);

You might add further formatting options such as minimal output width, fill characters, precision, ... – just as printf provides as well...

Sure, this answer does not match exactly your question ("how to produce a warning"), instead, it simply makes the warning obsolete...

Aconcagua
  • 24,880
  • 4
  • 34
  • 59
0

Ok, I hoped someone else said it, but I guess I'll be the one to bring macros...

#define LogInfo(logger, format, ...) printf("%s " format, logger.header().c_str(), __VA_ARGS__);

In order to illustrate what can be achieved, I assumed you wanted to add the logger header at each line. This is just an example.

You'd use it that way:

#include <cstdlib>
#include <string>
#include <iostream>

class Logger {
public:
    Logger(std::string header = "") : header_(header) {}
    std::string const& header() const { return header_; }
private:
    std::string header_;
};

#define LogInfo(logger, format, ...) printf("%s " format, logger.header().c_str(), __VA_ARGS__);

int main()
{
    const char* s = "Monty Python";
    Logger logger("[Header]");
    //LogInfo(logger, "%d", s); // error: format '%d' expects argument of type 'int', but argument 3 has type 'const char*' [-Werror=format=]
    LogInfo(logger, "%s", s); // [Header] Monty Python
}

Demo: http://coliru.stacked-crooked.com/a/ad698776f2b0ed4f

YSC
  • 38,212
  • 9
  • 96
  • 149
0

As pointed out in comments the printf format errors can be used through the format attribute. But you have to loose the vardiag templates for that or add another level of indirection from the vardiac template function to a simple C vardiac function.

The format specifier is implicit in gcc (and other compilers) definition of printf and explicit for many other printf like functions. e.g.

extern int vsnprintf (char *__restrict __s, size_t __maxlen,
                  const char *__restrict __format, _G_va_list __arg)
 __THROWNL __attribute__ ((__format__ (__printf__, 3, 0)));

Because of the attribute the vsnprintf will give the same warnings as a plain printf does. See the linked docs for how to specify the format attribuet for your function (after loosing the vardiac template). Note: Conversion to a plain vardiac function means you have to call vprintf using the varargs macros of the compiler.

Goswin von Brederlow
  • 11,875
  • 2
  • 24
  • 42