Sunday, March 20, 2011

Is this possible use ellipsis in macro? Can it be converted to template?

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?

From stackoverflow
  • 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.

    xtofl : that wouldn't accomplish the asker's wish to include the __FILE__ and __LINE__ arguments. These can only be inserted by a macro.
  • 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 ?

    bgee : 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 ...
    Bluebird75 : 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.
  • 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;
    }
    
    Johannes Schaub - litb : +1, exactly what i had in mind
    bgee : 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...
    xtofl : 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.
  • 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 : Indeed, a solution where you chain operations would work well, too. But why would you mix the "operator<<" and the "operator," notation, instead of using the 'traditional' ostream& operator( ostream&, T )?

0 comments:

Post a Comment