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   Members   

154
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.



#2   Moderators   

49401
Like
10Likes
Like

Posted 03 April 2014 - 12:08 AM

*
POPULAR

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



#3   Prime Members   

1720
Like
0Likes
Like

Posted 03 April 2014 - 12:20 AM

Is printf not ok?



#4   Members   

13100
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.


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


#5   Members   

560
Like
10Likes
Like

Posted 03 April 2014 - 12:38 AM

*
POPULAR

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   Members   

1875
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   Members   

5901
Like
5Likes
Like

Posted 03 April 2014 - 03:43 AM

*
POPULAR

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   Members   

966
Like
1Likes
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   Members   

12271
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   Members   

1252
Like
6Likes
Like

Posted 03 April 2014 - 08:15 AM

*
POPULAR

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   Members   

12271
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   Members   

33503
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' or 'SotL' rather than copy+pasting it all the time.
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.
Of Stranger Flames - [indie turn-based rpg set in a para-historical French colony] | Indie RPG development journal | [Fly with me on Twitter]

#13   Members   

4764
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   Members   

16760
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

Game Developer, C++ Geek, Dragon Slayer - http://seanmiddleditch.com

C++ SG14 "Games & Low Latency" - Co-chair - public forums

Wargaming Seattle - Lead Server Engineer - We're hiring!


#15   Members   

2081
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   Members   

2010
Like
0Likes
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   Members   

12271
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   Members   

2010
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   Members   

12271
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   Moderators   

21389
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.
Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]




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.