Question

Having implemented CLogClass to make decent logging I also defined macro, but it works only with one parameter...

class CLogClass
{ 
public:
       static void DoLog(LPCTSTR sMessage, ...);
};
#define DebugLog(sMessage, x) ClogClass::DoLog(__FILE__, __LINE__, sMessage, x)

Well, it fails when called with more than 2 parameters :( ... Is it possible at all to avoid it? Can it be translated to templates somehow?

EDIT: Variadic macros were introduced in VS 2005 (But i'm currently in VS 2003...). Any advices?

Was it helpful?

Solution

You could have a MYLOG macro returning a custom functor object which takes a variable number of arguments.

#include <string>
#include <cstdarg>

struct CLogObject {

  void operator()( const char* pFormat, ... ) const {
    printf( "[%s:%d] ", filename.c_str(), linenumber );
    va_list args;
    va_start( args, pFormat );
    vfprintf( stderr, pFormat, args );
    va_end( args );
  }

  CLogObject( std::string filename, const int linenumber )
    : filename( filename ), linenumber( linenumber )
  {}
  std::string filename;
  int linenumber;
};

#define MYLOG CLogObject( __FILE__, __LINE__ )


int _tmain(int argc, _TCHAR* argv[])
{

  MYLOG( "%s, %d", "string", 5 );
  return 0;
}

Note that it's not so hard to step over to the type-safe variant touched by this answer: you don't need any variadic arguments due to the chaining effect of operator<<.

struct CTSLogObject {

  template< typename T >
  std::ostream& operator<<( const T& t ) const {
    return std::cout << "[" << filename << ":" << linenumber << "] ";
  }

  CTSLogObject( std::string filename, const int linenumber )
    : filename( filename ), linenumber( linenumber )
  {}
  std::string filename;
  int linenumber;
};
#define typesafelog CTSLogObject( __FILE__, __LINE__ )

int _tmain(int argc, _TCHAR* argv[])
{
  typesafelog << "typesafe" << ", " << 5 << std::endl;
  return 0;
}

OTHER TIPS

Your questions actually appeals to two answers. You want to do the universal logging function, that works like printf but can be fully customise. So you need:

  • macro taking a variable number of arguments
  • function taking a variable number of arguments

Here is your code example adatapted:

#include <stdio.h>
#include <stdarg.h>


class CLogClass
{
public:
    static void DoLogWithFileLineInfo( const char * fmt, ... )
    {
        va_list ap;
        va_start( ap, fmt );
        vfprintf( stderr, fmt, ap );
        va_end( ap );
    }

};


#define MYLOG(format, ...) CLogClass::DoLogWithFileLineInfo("%s:%d " format , __FILE__, __LINE__, __VA_ARGS__)

int main()
{
    MYLOG("Hello world!\n", 3); // you need at least format + one argument to your macro
    MYLOG("%s\n", "Hello world!");
    MYLOG("%s %d\n", "Hello world!", 3);
}

Variadic macros have been introduced in C99, so it will work on compilers supporting C99 or C++0x . I tested it successfully with gcc 3.4.2 and Visual Studio 2005.

Variadic arguments to functions have been there forever so no worry about compability here.

It's probably possible to do it with some template meta-programmaing but I don't see the interest of it given the simplicity of the code above.

As a last note, why use a static method in an empty class instead of a function ?

class Log {
    stringstream buffer;
    public:
        class Proxy {
            public:
                Proxy(Log& p) : parent(p) {}
                template<class T>
                Proxy& operator,(T const& t) {
                    parent.buffer << t;
                    return *this;
                }
                ~Proxy() {
                    parent.buffer << endl;
                    cout << parent.buffer.str();
                    parent.buffer.str("");
                }
            private:
                CLog& parent;
        };

        template<class T>
        Proxy operator<<(T const& t) {
            buffer << t;
            return Proxy(*this);
        }
};

Can be trivially extended to write timestamps, check for loglevel, write to file, etc.

Or, more simply but less flexibly:

class Log {
    public:
        class Proxy {
            public:
                template<class T>
                Proxy& operator,(T const& t) {
                    cout << t;
                    return *this;
                }
                ~Proxy() {
                    cout << endl;
                }
        };

        template<class T>
        Proxy operator<<(T const& t) {
            cout << t;
            return Proxy();
        }
};

Usage:

Log log;
void f() {
     log << "hey, my age is ", age;
}

I would tend to use a globally visible extern function rather than a macro in this instance, and resolve the ellipsis in this function using a va_list. See my previous post for an example on how to achieve this.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top