Click here to Skip to main content
15,881,248 members
Articles / Programming Languages / C++
Article

Defining a unique macro receiving multiple arguments of any type

Rate me:
Please Sign up or sign in to vote.
4.34/5 (36 votes)
28 Dec 20032 min read 95.2K   518   27   21
This article describes how to define a macro receiving multiple arguments of multiple types and convert it automatically to a concatenated string

Introduction

One of the common use of C++ macros is to deal with functions like tracing or logging to a file. As these functions usually works with strings generated by dynamic data, passing multiple arguments to the macro without having to convert each one to a string may be useful, and sometimes the developer don't know the way to go. It can be made by a lot of ways, and the Code Project have similar articles on this topic but the solution I present here use a different approach that result in a clean syntax without character formatting like "%d" / "%s" / "%c"... a sample usage may be:

MYTRACE( "The username found is ", strUserName,
         " having ", intAge,
         " years old. The database key is ", lngUserKey );

I'll use tracing as an example application, a real tracing class may consider issues like multithreading code protection and others that are out the scope of this article.

Background

Suppose you have to trace information in your code. Usually you'll encapsulate it in a class:

class MyTraceClass
{
   static void Trace( const string &message )
   {
      // trace code...
   }
};

As we need a easy way to disable the tracing code on release builds, we'll define a macro, like:

#define MYTRACE( x ) MyTraceClass::Trace( x )
It works fine, but this way we can pass just one string argument. Some programmers define various macro versions like TRACE / TRACE2 / TRACE3... one for each number of arguments, clearly it isn't an elegant solution because the macro user need to know the correct macro version accordingly the arguments being passed. Using va_arg the problem can be solved. The MFC defines the TRACE macro as:
void AFX_CDECL AfxTrace(LPCTSTR lpszFormat, ...);
.
.
.
#define TRACE  ::AfxTrace
So we can pass many arguments to the macro using the printf format specifiers like "%d" / "%s" / "%c"...
int i=10;
.
.
.
   TRACE( "Iteration number:  %d", i );

The code above works fine but we have to be aware of the format specifiers, and we can't pass a non-LPCTSTR data as the first argument to TRACE without manually convert to LPCTSTR. As the tracing code will be "turned off", any aditional code is a overhead and will clutter the rest of code.

The proposed solution

Using templates and overloading, the macro can be called with a variable number of arguments of any type. The stringstream class helps to easily convert each argument to string, composing the final information.

First, the macro will be defined to the name of the static method:

#define MYTRACE  MyTraceClass::Trace

To receive arguments of any type, we'll define the tracing function receiving typename arguments:

template<typename T1> static void Trace( T1 par1 );

To receive multiple arguments, provide various versions of the overloaded method. Now the arguments can be converted to string using the stringstream class:

template<typename T1, typename T2> static void Trace( T1 par1, T2 par2 )
{
stringstream ss;
   ss << par1 << par2;
   Trace( ss.str() );
}

template<typename T1, typename T2, typename T3> 
  static void Trace( T1 par1, T2 par2, T3 par3 )
{
stringstream ss;
   ss << par1 << par2 << par3;
   Trace( ss.str() );
}

template<typename T1, typename T2, typename T3, typename T4> 
  static void Trace( T1 par1, T2 par2, T3 par3, T4 par4 )
{
stringstream ss;
   ss << par1 << par2 << par3 << par4;
   Trace( ss.str() );
}

.
.
.

This way we can call the macro with syntaxes like below. The screen illustrates a simple cout output.

MYTRACE( "Method Add() Called !" );
MYTRACE( "Iteration number: ", i );
MYTRACE( "The username found is ", strUserName,
         " having ", intAge,
         " years old. The database key is ", lngUserKey );

Image 1

Obviously the number of arguments can't exceed the maximum defined on overloaded methods, but it cannot be considered a problem, tracing / logging code usually don't need too much arguments, at worse it's easy to extend the class to support more overloaded methods.

This is my first article on Code Project. I hope someone might find it useful :)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Brazil Brazil
I live in São Paulo - Brazil - where I have my tiny C++ \ Win32 \ .NET training company: NEXSUN
I have worked with many programming languages like VB / C++ / Java / C# but I´m mainly interested in: C++ forever Smile | :) and the .NET framework. Now I'm IBM-OOAD, OMG-OCUP, MCP, MCAD, MCSD, SCJP, MCSD.NET, MCTS, MCPD.

Comments and Discussions

 
GeneralATLTRACE Pin
nyc12322-Jan-04 9:34
nyc12322-Jan-04 9:34 
GeneralRe: ATLTRACE Pin
GuimaSun2-May-04 4:59
GuimaSun2-May-04 4:59 
Generalcool. and there is another way Pin
timepalette3-Jan-04 18:47
timepalette3-Jan-04 18:47 
GeneralRe: cool. and there is another way Pin
kozlowski4-Jan-04 12:50
kozlowski4-Jan-04 12:50 
GeneralRe: cool. and there is another way Pin
zark925-Jan-04 1:52
zark925-Jan-04 1:52 
GeneralRe: cool. and there is another way Pin
kozlowski5-Jan-04 22:47
kozlowski5-Jan-04 22:47 
GeneralRe: cool. and there is another way - An alternative Pin
heyto6-Jan-04 4:36
heyto6-Jan-04 4:36 
GeneralRe: cool. and there is another way - An alternative Pin
kozlowski6-Jan-04 14:39
kozlowski6-Jan-04 14:39 
GeneralRe: cool. and there is another way - An alternative Pin
heyto7-Jan-04 21:47
heyto7-Jan-04 21:47 
GeneralRe: cool. and there is another way - An alternative Pin
kozlowski7-Jan-04 22:43
kozlowski7-Jan-04 22:43 
Fair point because I wanted to show simple example.
Most programmers are lazy and doesn't use const to indicate the members of class without side effects. It's very likely that somebody will use the function with side effect inside TRACE.
i.e.
TRACE(DataStore.ComputeSomething());
I can agree that it is disadvantage of the TRACE, because Release code works differently than original.

About LOG let's try:
LOG(flags & 0xC5)
It is still far from reliability of the original TRACE Smile | :)
You see how difficult it is to define such macro, in really good way.

Greetz
GeneralRe: cool. and there is another way - An alternative Pin
heyto8-Jan-04 3:41
heyto8-Jan-04 3:41 
GeneralRe: cool. and there is another way - An alternative Pin
kozlowski8-Jan-04 4:26
kozlowski8-Jan-04 4:26 
GeneralRe: cool. and there is another way - An alternative Pin
heyto8-Jan-04 7:04
heyto8-Jan-04 7:04 
GeneralRe: cool. and there is another way - An alternative Pin
kozlowski8-Jan-04 22:23
kozlowski8-Jan-04 22:23 
GeneralRe: cool. and there is another way - An alternative Pin
heyto8-Jan-04 23:22
heyto8-Jan-04 23:22 
GeneralRe: cool. and there is another way - An alternative Pin
kozlowski10-Jan-04 0:06
kozlowski10-Jan-04 0:06 
QuestionUse template defaults? Pin
SBarney31-Dec-03 11:10
SBarney31-Dec-03 11:10 
AnswerRe: Use template defaults? Pin
GuimaSun1-Jan-04 8:12
GuimaSun1-Jan-04 8:12 
GeneralRe: Use template defaults? Pin
Anonymous2-Jan-04 8:26
Anonymous2-Jan-04 8:26 
GeneralRe: Use template defaults? Pin
GuimaSun3-Jan-04 16:20
GuimaSun3-Jan-04 16:20 
GeneralCool! Pin
Jason Troitsky29-Dec-03 22:53
Jason Troitsky29-Dec-03 22:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.