Jump to content

  • Log In with Google      Sign In   
  • Create Account


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


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
23 replies to this topic

#1 Nübček Pænus   Members   -  Reputation: 153

Like
0Likes
Like

Posted 02 April 2014 - 11:56 PM

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.



Sponsor:

#2 Hodgman   Moderators   -  Reputation: 27761

Like
10Likes
Like

Posted 03 April 2014 - 12:08 AM

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



#3 ultramailman   Prime Members   -  Reputation: 1558

Like
0Likes
Like

Posted 03 April 2014 - 12:20 AM

Is printf not ok?



#4 Bacterius   Crossbones+   -  Reputation: 8177

Like
1Likes
Like

Posted 03 April 2014 - 12:26 AM

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.


The slowsort algorithm is a perfect illustration of the multiply and surrender paradigm, which is perhaps the single most important paradigm in the development of reluctant algorithms. The basic multiply and surrender strategy consists in replacing the problem at hand by two or more subproblems, each slightly simpler than the original, and continue multiplying subproblems and subsubproblems recursively in this fashion as long as possible. At some point the subproblems will all become so simple that their solution can no longer be postponed, and we will have to surrender. Experience shows that, in most cases, by the time this point is reached the total work will be substantially higher than what could have been wasted by a more direct approach.

 

- Pessimal Algorithms and Simplexity Analysis


#5 Scarabus2   Members   -  Reputation: 556

Like
10Likes
Like

Posted 03 April 2014 - 12:38 AM

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


visualnovelty.com - Novelty - Visual novel maker

#6 Stainless   Members   -  Reputation: 677

Like
0Likes
Like

Posted 03 April 2014 - 03:06 AM

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.



#7 Erik Rufelt   Crossbones+   -  Reputation: 3067

Like
5Likes
Like

Posted 03 April 2014 - 03:43 AM

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);
}


#8 Zao   Members   -  Reputation: 878

Like
2Likes
Like

Posted 03 April 2014 - 04:27 AM

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.

#9 Aardvajk   Crossbones+   -  Reputation: 5269

Like
2Likes
Like

Posted 03 April 2014 - 05:23 AM

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.


Edited by Aardvajk, 03 April 2014 - 05:25 AM.


#10 kloffy   Members   -  Reputation: 867

Like
6Likes
Like

Posted 03 April 2014 - 08:15 AM

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;
}

Edited by kloffy, 03 April 2014 - 08:30 AM.


#11 Aardvajk   Crossbones+   -  Reputation: 5269

Like
0Likes
Like

Posted 03 April 2014 - 08:17 AM


Ouch, all those template arguments. My shot at a C++11 version:

 

Cheers for that, not got my head round this stuff yet but that will be a big help.



#12 Servant of the Lord   Crossbones+   -  Reputation: 17185

Like
2Likes
Like

Posted 03 April 2014 - 12:32 PM

Here's what I currently use for formatting:
 
/*
	Creates a string from 'str', with every occurrence of "%n" replaced with the nth argument.

	Example: String::Format("The time is now %1 on the %3 of %2", {time, month, day})

	It starts with 1%. 0% is not understood.
*/
std::string Format(std::string text, const std::vector<std::string> &arguments)
{
	std::vector<std::string> symbols = {"%1", "%2", "%3", "%4", "%5", "%6", "%7", "%8", "%9", "%10", "%11", "%12", "%13", "%14", "%15"};

	unsigned int i = 0;
	for(const auto &arg : arguments)
	{
		if(i < symbols.size())
		{
			text = String::ReplaceAll(text, symbols[i], arg);
		}

		i++;
	}

	return text;
}
Just a quick and dirty one without many features (and Schlemiel algorithm'd too - been meaning to rewrite it next time I'm bored) - and I manually convert the arguments to strings when passing them in:
//Converts the version struct to a string like: "2013.07.22.2235"
std::string cDateVersion::ToString(bool includeHourSeconds) const
{
	return String::Format((includeHourSeconds? "%1.%2.%3.%4%5" : "%1.%2.%3"),
                              {std::to_string(this->year),
                              std::to_string(this->month),
                              std::to_string(this->day),
                              std::to_string(this->hour),
                              std::to_string(this->minute)});
}
 
But by mixing variadic templates (like kloffy shows), and an overloaded or templated function for string conversion, you could make a very powerful replacement for sprintf.

Edited by Servant of the Lord, 03 April 2014 - 12:33 PM.

It's perfectly fine to abbreviate my username to 'Servant' rather than copy+pasting it all the time.

[Fly with me on Twitter] [Google+] [My broken website]

All glory be to the Man at the right hand... On David's throne the King will reign, and the Government will rest upon His shoulders. All the earth will see the salvation of God.                                                                                                                                                       [Need free cloud storage? I personally like DropBox]

Of Stranger Flames - [indie turn-based rpg set in a para-historical French colony] | Indie RPG development journal


#13 ChaosEngine   Crossbones+   -  Reputation: 2142

Like
0Likes
Like

Posted 03 April 2014 - 02:01 PM


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.

 

 "Basically whenever you invoke the dread ellipses construct you leave the happy world of type safety." -- SiCrane

 

:D


if you think programming is like sex, you probably haven't done much of either.-------------- - capn_midnight

#14 SeanMiddleditch   Members   -  Reputation: 3895

Like
2Likes
Like

Posted 03 April 2014 - 02:41 PM

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'm desperately hoping that was just hyperbole and that your company didn't write its own broken printf that had 1460 bugs in it that nobody could fix within 4 years. smile.png

#15 osmanb   Crossbones+   -  Reputation: 1458

Like
2Likes
Like

Posted 04 April 2014 - 07:34 AM

You don't HAVE to abandon type safety - if you have a decent compiler (GCC, Clang, some others), you can put an attribute on any function that uses printf style format strings, and the compiler will do type checking of the varargs against the string.http://stackoverflow.com/questions/996786/how-to-use-the-gcc-attribute-format



#16 Alundra   Members   -  Reputation: 773

Like
-1Likes
Like

Posted 04 April 2014 - 07:50 AM


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

Boost is ok for unit-test but should not be used since it's heavy weight.

In my opinion, vsprintf or use just a lib for that is a better choice.

EDIT: (it's funny like the truth always give bad point :))


Edited by Alundra, 04 April 2014 - 08:57 AM.


#17 Aardvajk   Crossbones+   -  Reputation: 5269

Like
1Likes
Like

Posted 04 April 2014 - 09:38 AM


EDIT: (it's funny like the truth always give bad point )

 

Its even funnier that you think downratings suggest your view is the truth.



#18 Alundra   Members   -  Reputation: 773

Like
0Likes
Like

Posted 04 April 2014 - 09:49 AM

 


EDIT: (it's funny like the truth always give bad point )

 

Its even funnier that you think downratings suggest your view is the truth.

 

I'm not the only one to say that, a lot of professional think the same and it's for that boost is not used.


Edited by Alundra, 04 April 2014 - 09:50 AM.


#19 Aardvajk   Crossbones+   -  Reputation: 5269

Like
1Likes
Like

Posted 04 April 2014 - 10:30 AM

You don't HAVE to abandon type safety - if you have a decent compiler (GCC, Clang, some others), you can put an attribute on any function that uses printf style format strings, and the compiler will do type checking of the varargs against the string.http://stackoverflow.com/questions/996786/how-to-use-the-gcc-attribute-format

 

Interesting, didn't know about that. As has been said, you can also work around the buffer overflow potential if you are careful (not actually relevant to printf, fprintf etc anyway).

 

The main remaining problem for me though is not solvable with varadic args - support for new types. You instead need to implement .toString() methods on your types as the only sensible alternative.


Edited by Aardvajk, 04 April 2014 - 10:33 AM.


#20 ApochPiQ   Moderators   -  Reputation: 14294

Like
3Likes
Like

Posted 04 April 2014 - 10:52 AM

I'm not the only one to say that, a lot of professional think the same and it's for that boost is not used.


Being a "professional" is completely orthogonal to being right.




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS