views:

647

answers:

4

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?

A: 

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.

Shane MacLaughlin
that wouldn't accomplish the asker's wish to include the __FILE__ and __LINE__ arguments. These can only be inserted by a macro.
xtofl
+2  A: 

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 ?

Bluebird75
All this works... besides of the macro - as I said ellipsis is not working in macro in VS2003. See - http://msdn.microsoft.com/en-us/library/ms177415(VS.80).aspx ...
bgee
If you can't use a variadic macro, that's more difficult. I remember a cppjournal article about complex trick with two macros, I'll see if I can find it again.
Bluebird75
+3  A: 

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;
}
xtofl
+1, exactly what i had in mind
Johannes Schaub - litb
It's seems almost right... but chaining attributes into one stream is not a problem... The real problem is to serialize class containing all arguments through process boundary... and deserialize them after... So it's good solution but still not an answer...
bgee
Did you mention the serialization thing in the question? It looked as if you needed a mechanism to add a variable number of arguments to some compile-time known literals.
xtofl
A: 
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;
}
xtofl
To emphasize that the semantics are _not_ the same as ostream's. In particular, that << begins a log entry (potentially a timestamp) and the end of the full-expression ends the log-entry (automatic endline or whatever). I'm open to a better solution, I just don't believe imitating ostream to be it.