Click here to Skip to main content
15,878,809 members
Articles / Programming Languages / C++98

Debug Print in Variadic Template Style

Rate me:
Please Sign up or sign in to vote.
4.81/5 (10 votes)
12 Apr 2016CPOL6 min read 39.1K   484   26   3
Debug Print using C++11 Variadic Template

Table of Contents

Introduction

C++11 (formerly known as C++0x) is the most recent version of the standard of the C++ programming language. It was approved by ISO on 12 August 2011, replacing C++03. C++11 introduces a number of new features to the language and standard library. Notably, when Visual C++ 11 is released, variadic template support, among other features, are missing. Microsoft recently released the Visual C++ Compiler November 2012 CTP which adds the following C++11 features: uniform initialization, initializer lists, variadic templates, function template default arguments, delegating constructors, explicit conversion operators and raw strings. A Channel 9 video lecture by Stephan T. Lavavej covers more details about these newly added features. Variadic template enables C++ programmers to write template class and functions with arbitrary number of types. This article mainly focuses on the variadic template and using that to implement DebugPrint.

Variadic Printf

C++
int printf ( const char * format, ... );

C style variadic printf (and its cousins, like fprintf and sprintf) exists since the invention of C language in 1970s, is inherently unsafe. The type specified in the format string, can be mismatched by the actual data type of the argument given to printf.

C++
int n = -1; // signed integer
printf("%u", n); // %u display 4294967295

Variadic template in C++11 allows template writers to create templates which takes in any number of (different) types; Now we can use this new feature to implement a "safe printf" function. Variadic template function is usually implemented recursively.

C++
// base function which stops the recursion.
void safe_printf(const char *s);

// variadic recursive function
template<typename T, typename... Args>
void safe_printf(const char *s, T value, Args... args);

We know Args is variadic as indicated by the typename... Args and Args... args. Args can be zero or more types. When it is empty, the base safe_printf is called. Notice the base safe_printf is not variadic! Let us see the function body.

C++
void safe_printf(const char *s)
{
    while (*s) {
        if (*s == '%') {
            if (*(s + 1) == '%') {
                ++s;
            }
            else {
                throw std::runtime_error("invalid format string: missing arguments");
            }
        }
        std::cout << *s++;
    }
}

template<typename T, typename... Args>
void safe_printf(const char *s, T value, Args... args)
{
    while (*s) {
        if (*s == '%') {
            if (*(s + 1) == '%') {
                ++s;
            }
            else {
                std::cout << value;
                safe_printf(s + 1, args...); // call even when *s == 0 to detect extra arguments
                return;
            }
        }
        std::cout << *s++;
    }
    throw std::logic_error("extra arguments provided to printf");
}

To clarify a bit, the function is actually not truly recursive. A traditional recursive function will call itself directly or indirectly. When variadic template function calls itself, it is actually calling a different function with the same name but have different number of arguments. This safe_printf is copied from the wikipedia page. The safe_printf calls cout to do its work and therefore can display any arbitrary types as long as the programmer overloads the << operator for that type. It throws runtime_error when there is not enough arguments and it also throws logic_error when there is extra arguments. Here is how to call it.

C++
safe_printf("Product:%, Qty:%, Price is $%\n ", "Shampoo", 1200, 2.65);
// prints "Product:Shampoo, Qty:1200, Price is $2.65"

Notice: We do not need to specify the types in the format string: we only use % as a placeholder. To print % literally, write %% to escape it. C++ Programmer can use sizeof...(args) to find out the number of arguments at compile time. The call sizeof(args)... expand to call the sizeof for every argument. User can do this for any function, not just sizeof.

Note: This article focuses on writing function, not class. To know more about writing variadic template class, please refer to the video link in the related section.

Variadic Debug Print

Prior to variadic Debug Print, programmers typically make use of sprintf to format their text before displaying them with OutputDebugString.

C++
char buf[50];
sprintf(buf, "Product:%s, Qty:%d, Price is $%f\n ", "Shampoo", 1200, 2.65);
OutputDebugStringA(buf);

A problem with sprintf is the given buffer may not be large enough to hold resultant text. So GNU and BSD came out with asprintf and vasprintf as extension of C or POSIX. The functions asprintf and vasprintf are analogs of sprintf and vsprintf, except that they allocate a string large enough to hold the output including the terminating null byte, and return a pointer to it via the first argument. This pointer should be passed to free to release the allocated storage when it is no longer needed. Note: These functions are only available on Linux and BSD.

C++
char *pbuf = NULL;
asprintf(&pbuf, "Product:%s, Qty:%d, Price is $%f\n ", "Shampoo", 1200, 2.65);
/* use here */
free(pbuf);

In this section, we will convert the safe_printf to a version which prints debugging information, using Windows API OutputDebugString, instead of outputting to console using cout.

C++
#include <sstream>
#include <Windows.h>

void xsprintf(std::string& result, const char *s)
{
    while (*s) {
        if (*s == '%') {
            if (*(s + 1) == '%') {
                ++s;
            }
            else {
                throw std::runtime_error("invalid format string: missing arguments");
            }
        }
        result += *s++;
    }
}

template<typename T, typename... Args>
void xsprintf(std::string& result, const char *s, T value, Args... args)
{
    while (*s) {
        if (*s == '%') {
            if (*(s + 1) == '%') {
                ++s;
            }
            else {
                std::stringstream stream;
                stream << value;
                result += stream.str();
                xsprintf(result, s + 1, args...); // call even when *s == 0 to detect extra arguments
                return;
            }
        }
        result += *s++;
    }
    throw std::logic_error("extra arguments provided to printf");
}

template<typename... Args>
void DebugPrint(const char *s, Args... args)
{
#ifdef _DEBUG
    std::string result = "";
    xsprintf(result, s, args...);

    OutputDebugStringA(result.c_str());
#endif
}

The problem with recursive method, every function call have to carry stateful information (if any). DebugPrint has a state information known as the result; We cannot print the information piece by piece, as with cout because OutputDebugString will break them up into new lines in Sysinternals DebugView (not in Visual Studio debugger though). And we do not want its caller to know this implementation detail. Therefore DebugPrint cannot be recursive so it calls a recursive function, xsprintf to do its work. xsprintf makes use of stringstream to do its work. stringstream can be used to convert POD types into string (using <<) and vice-versa (using >>).

The way to call DebugPrint is similar to the previous example.

C++
DebugPrint("Product:%, Qty:%, Price is $%\n ", 
	"Shampoo", 1200, 2.65); // prints "Product:Shampoo, Qty:1200, Price is $2.65"

Variadic Debug Print (.NET style)

What if we prefer .NET string placeholder specifier like {0} for the 1st argument and {1} for 2nd argument and so forth? I have done that version as well. In addition, I have make it unicode. But this version will not throw any exceptions when there are missing or extra arguments: the programmer will notice this discrepancy in the output anyway. This is to follow the behavior of original non-variadic DebugPrint (covered in the next section).

C++
#include <string>
#include <sstream>
#include <Windows.h>

std::wstring Anchor( int i )
{
    std::wstringstream stream;
    stream << i;

    std::wstring str = L"{";
    str += stream.str() + L"}";

    return str;
}

std::wstring Replace( std::wstring fmtstr, size_t index, const std::wstring& s )
{
    size_t pos = 0;
    std::wstring anchor = Anchor( index );

    while( std::wstring::npos != pos )
    {
        pos = fmtstr.find( anchor, pos );

        if( std::wstring::npos != pos )
        {
            fmtstr.erase( pos, anchor.size() );
            fmtstr.insert( pos, s );
            pos += s.size();
        }
    }

    return fmtstr;
}

std::wstring Format( std::wstring fmt, size_t index )
{
    return fmt;
}

template<typename T, typename... Args>
std::wstring Format( std::wstring fmt, size_t index, T& t, Args&... args )
{
    std::wstringstream stream;
    stream << t;

    std::wstring result = Replace( fmt, index, stream.str() );

    ++index;

    std::wstring str = Format( result, index, args... );

    return str;
}

template<typename... Args>
void DebugPrint(const wchar_t *s, Args... args)
{
#ifdef _DEBUG
    std::wstring str = Format(std::wstring(s), 0, args...);

    OutputDebugStringW(str.c_str());
#endif
}

DebugPrint calls the recursive Format function to do its work. Anchor function returns the "{x}" string based on the index and Replace will use this anchor to find and replace them with the arguments. .NET custom formatting specifier (like "{0:f2}") are not supported. In other words, user is better off using the previous % version, as "{x}" serves no purpose other than slowing down the processing time to find and replace them.

Non-variadic Debug Print (.NET style)

C++
void Print( const wchar_t* fmt, Box D1, Box D2 );

Prior to this, there is a version of DebugPrint class written in 2007, which makes heavy use of Template Pattern (pattern has nothing to do with C++ templates). This class has overloaded Print functions which varies from having 0 to 10 Box arguments to provide a illusion of variadic and is no longer actively maintained. If user needs to print 11 variables, he/she is out of luck. Box works by providing a overloaded constructor for each POD type and convert the POD variable into a string.

C++
print(L"{0}{1}", 1,    1.0f);
print(L"{0}{1}", 1.0f, 1.0f);
print(L"{0}{1}", 1,    1);
print(L"{0}{1}", 1.0f, 1);

Let's say we compile the above code once using variadic template version and another using the overloaded version. The variadic template version will instantiate 4 different version (see below) maybe would result in code bloat if there are many different combinations used (at least in theory). However, generally, variadic template code is found to have less binary size and faster compilation times.

C++
void print(wstring, int,   float);
void print(wstring, float, float);
void print(wstring, int,   int);
void print(wstring, float, int);

For the overload version, the same overloaded function is called (see below) for 4 times. Another advantage is the overloaded version can be used with older compiler if the option to use latest C++11 compiler is not available.

C++
void print(wstring, box,   box);

Source Code

To use the new C++11 features provided by the Visual C++ Compiler November CTP, the user have to manually select the compiler in the project configuration, under General. Please note: 'Microsoft Visual C++ Compiler Nov 2012 CTP' is for testing purposes only.

Select VC Nov CTP in Project Configuration 

Conclusion

We have seen that variadic template function is usually implemented recursively (though not true recursion as mentioned previously). The difference between overloaded functions and variadic template is mentioned. Readers are strongly encouraged to download and examine the source code.

Related Links

As mentioned above, if the reader is interested to find out more about variadic templates, especially for variadic template class and how std::tuple is implemented, I would highly recommend to watch this informative video.

I would humbly recommend anyone to visit the link below, if he/she is keen on a C++11 variadic template library to read and write files.

History

  • 2012-12-30: Included a short asprintf paragraph
  • 2012-12-23: Initial release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Singapore Singapore
Shao Voon is from Singapore. His interest lies primarily in computer graphics, software optimization, concurrency, security, and Agile methodologies.

In recent years, he shifted focus to software safety research. His hobby is writing a free C++ DirectX photo slideshow application which can be viewed here.

Comments and Discussions

 
Questionwhat if type specification is desired Pin
jinstrong28-May-18 3:07
jinstrong28-May-18 3:07 
AnswerRe: what if type specification is desired Pin
Shao Voon Wong28-May-18 3:22
mvaShao Voon Wong28-May-18 3:22 
SuggestionExcellent Article, but printf should be parameterized by the character type to be used. Pin
Johann Anhofer10-Mar-13 23:31
Johann Anhofer10-Mar-13 23:31 

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.