[C++] Is there an easy way to mimic printf()'s behavior?

Started by
22 comments, last by Servant of the Lord 10 years ago

Topic title says it all. I would like to turn this mess:


char string[100];
sprintf(string, "Error: %s at %08X with parameter %i and size %.2f", szA, pB, iC, fD);
pretty_fatal_error_thrower(string);

into this beauty:


pretty_fatal_error_thrower("Error: %s at %08X with parameter %i and size %.2f", szA, pB, iC, fD);

Can it be done without basically re-writing printf() from scratch? That's pretty much all I get with a Google search. I would switch to C++ streams except for the minuscule yet important detail that streaming does not support custom formatting such as "%08X". Thanks in advance.

Advertisement

Check out vsprintf, or for C++, boost::format.

Is printf not ok?

Is printf not ok?

I think he wants to automatically do other things when that function is called in addition to printf'ing (like wrapping the function) so, yeah, a variadic function with vsprintf - or a variadic macro - would be the usual way to do this in C99 as Hodgman suggested, and of course if you go C++ there's probably plenty of alternatives.

“If I understand the standard right it is legal and safe to do this but the resulting value could be anything.”

Here's a fun drinking game: Take a drink every time a C++ question gets answered with "Boost".

visualnovelty.com - Novelty - Visual novel maker

I used to work at a company called Tao. In the four years I was there, not a single day went by without an active bug being logged on printf. smile.png

I would switch to strings and write my own hex formatting code for that one case.

Perhaps this would work:


template<typename ...Args>
void myPrint(const char *format, Args ...args) {
	char str[255];
	sprintf(str, format, args...);
	pretty_fatal_error_thrower(str);
}

Perhaps this would work:


	char str[255];

The core problem with using a C-style printf is that you have to either guess at the length of the resulting string or use one of the "safe" sized variants of printf and attempt with larger and larger buffers until the resulting string fits.

This is more pretty_fatal_stack_smash than pretty_fatal_message_format.

To make it is hell. To fail is divine.

In addition to Zao's points, you also have no type safety and can easily supply a parameter list that does not match the format string.

Here's a really naive header I use to provde type-safe string building. You could extend this easily enough:


#ifndef STRINGFORMAT_H
#define STRINGFORMAT_H

#include <string>
#include <sstream>

template<class T0, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8> std::string stringFormat(const T0 &t0, const T1 &t1, const T2 &t2, const T3 &t3, const T4 &t4, const T5 &t5, const T6 &t6, const T7 &t7, const T8 &t8)
{
    std::ostringstream os;
    os << t0 << t1 << t2 << t3 << t4 << t5 << t6 << t7 << t8;

    return os.str();
}

template<class T0, class T1, class T2, class T3, class T4, class T5, class T6, class T7> std::string stringFormat(const T0 &t0, const T1 &t1, const T2 &t2, const T3 &t3, const T4 &t4, const T5 &t5, const T6 &t6, const T7 &t7){ return stringFormat(t0, t1, t2, t3, t4, t5, t6, t7, ""); }
template<class T0, class T1, class T2, class T3, class T4, class T5, class T6> std::string stringFormat(const T0 &t0, const T1 &t1, const T2 &t2, const T3 &t3, const T4 &t4, const T5 &t5, const T6 &t6){ return stringFormat(t0, t1, t2, t3, t4, t5, t6, ""); }
template<class T0, class T1, class T2, class T3, class T4, class T5> std::string stringFormat(const T0 &t0, const T1 &t1, const T2 &t2, const T3 &t3, const T4 &t4, const T5 &t5){ return stringFormat(t0, t1, t2, t3, t4, t5, ""); }
template<class T0, class T1, class T2, class T3, class T4> std::string stringFormat(const T0 &t0, const T1 &t1, const T2 &t2, const T3 &t3, const T4 &t4){ return stringFormat(t0, t1, t2, t3, t4, ""); }
template<class T0, class T1, class T2, class T3> std::string stringFormat(const T0 &t0, const T1 &t1, const T2 &t2, const T3 &t3){ return stringFormat(t0, t1, t2, t3, ""); }
template<class T0, class T1, class T2> std::string stringFormat(const T0 &t0, const T1 &t1, const T2 &t2){ return stringFormat(t0, t1, t2, ""); }
template<class T0, class T1> std::string stringFormat(const T0 &t0, const T1 &t1){ return stringFormat(t0, t1, ""); }
template<class T0> std::string stringFormat(const T0 &t0){ return stringFormat(t0, ""); }

#endif // STRINGFORMAT_H

Usage


class SomeClass
{
};

// implement ostream overload for SomeClass

SomeClass c;
std::string s = stringFormat("There are ", 0.24f, " things and ", c, " works too");

You can just std::cout the result and throw your exception instead of returning the string. I'm sure better, more extensible ways of doing this now with varadic template args exist but they are beyond me at present.

Nice one Aardvajk. But ouch, all those template arguments. My shot at a C++11 version:
#include <iostream>
#include <iomanip>
#include <sstream>
#include <string>

namespace detail {

template<typename TOStream, typename... TArgs>
void print(TOStream& os, TArgs&&... args) {}

template<typename TOStream, typename TArg, typename... TArgs>
void print(TOStream& os, TArg&& arg, TArgs&&... args)
{
	print(os << std::forward<TArg>(arg), std::forward<TArgs>(args)...);
}

}

template<typename... TArgs>
std::string print(TArgs&&... args)
{
	std::ostringstream os;
	detail::print(os, std::forward<TArgs>(args)...);
	return os.str();
}

int main()
{
	std::cout << print("Hello", std::boolalpha, std::fixed, std::setprecision(2), ' ', 1, ' ', 2.0, ' ', true) << std::endl;
	return 0;
}

This topic is closed to new replies.

Advertisement