Sign in to follow this  
crocomire

snprintf c++ equivalent

Recommended Posts

The C function is still available, but the Right Way to do it in C++ is using iostreams, which are type-safe and won't be vunerable to buffer overflows. For example:

#include <iostream> // general iostream header
#include <sstream> // for std::stringstream
#include <string> // for std::string

int main(int argc, char *argv[])
{
int an_int = 0xf00d;
float a_float = 13.37f;
const char *a_message = "Hello, world!";

std::ostringstream ss;
ss << "The integer is: " << an_int << "\n"
"The float is: " << a_float << "\n"
"The message is: '" << a_message << "'" << std::endl;

// you can access the contents of the stringstream as a std::string, with:
std::string the_result = ss.str();

// and if you absolutely need a null-terminated const char* string, you can use:
the_result.c_str();

return 0;
}



John B

Share this post


Link to post
Share on other sites
Since the two major criticisms of stringstreams are their verbosity and lack of efficiency I've never understood why nobody has tried to write a stringstream based solution with functor based short format objects, i.e.:
/* warning hacked code, ruthlessly prematurely optimised (pessimised?) and generally nasty */
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>

namespace
{

unsigned int const right = 0;
unsigned int const left = 1;

}

class _d
{

public:

explicit _d(int value, int width = 0, int align = right)
:
value_(value),
width_(width),
align_(align)
{
}

template < typename TYPE >
std::basic_ostream< TYPE > & print(std::basic_ostream< TYPE > & ostream) const
{
writeToBuffer(value_);
unsigned int padWidth = std::max(width_ - int(numberBuffer_.size()), 0);
// fillPaddingBuffer(padWidth);
if (align_ == right)
{
// std::copy(paddingBuffer_.begin(), paddingBuffer_.begin() + padWidth, std::ostreambuf_iterator< char >(ostream));
std::fill_n(std::ostreambuf_iterator< char >(ostream)), padWidth, ' ');
}
std::copy(numberBuffer_.rbegin(), numberBuffer_.rend(), std::ostreambuf_iterator< char >(ostream));
if (align_ == left)
{
// std::copy(paddingBuffer_.begin(), paddingBuffer_.begin() + padWidth, std::ostreambuf_iterator< char >(ostream));
std::fill_n(std::ostreambuf_iterator< char >(ostream)), padWidth, ' ');
}
return ostream;
}

private:

/* static void fillPaddingBuffer(unsigned int size)
{
if (paddingBuffer_.size() < size)
{
paddingBuffer_.insert(paddingBuffer_.end(), size - paddingBuffer_.size(), ' ');
}
}*/


static void writeToBuffer(int value)
{
std::vector< char >::iterator bufferPosition = numberBuffer_.begin();
unsigned int uvalue;
if (value < 0)
{
uvalue = -value;
}
else
{
uvalue = value;
}
while (uvalue > 0 && bufferPosition != numberBuffer_.end())
{
*bufferPosition = (uvalue % 10) + '0';
uvalue /= 10;
++bufferPosition;
}
if (uvalue == 0)
{
if (bufferPosition != numberBuffer_.end())
{
numberBuffer_.resize(bufferPosition - numberBuffer_.begin());
}
}
else
{
while (uvalue > 0)
{
numberBuffer_.push_back((uvalue % 10) + '0');
uvalue /= 10;
}
}
if (value < 0)
{
if (bufferPosition != numberBuffer_.end())
{
*bufferPosition = '-';
++bufferPosition;
}
else
{
numberBuffer_.push_back('-');
}
}
}

int value_;
int width_;
int align_;

static std::vector< char > numberBuffer_;
// static std::vector< char > paddingBuffer_;

};

std::vector< char > _d::numberBuffer_;
//std::vector< char > _d::paddingBuffer_;

class _s
{

public:

explicit _s(std::string value, int width = 0, int align = right)
:
value_(value),
width_(width),
align_(align)
{
}

template < typename TYPE >
std::basic_ostream< TYPE > & print(std::basic_ostream< TYPE > & ostream) const
{
unsigned int padWidth = std::max(width_ - int(value_.size()), 0);
// fillPaddingBuffer(padWidth);
if (align_ == right)
{
// std::copy(paddingBuffer_.begin(), paddingBuffer_.begin() + padWidth, std::ostreambuf_iterator< char >(ostream));
std::fill_n(std::ostreambuf_iterator< char >(ostream)), padWidth, ' ');
}
ostream << value_;
if (align_ == left)
{
// std::copy(paddingBuffer_.begin(), paddingBuffer_.begin() + padWidth, std::ostreambuf_iterator< char >(ostream));
std::fill_n(std::ostreambuf_iterator< char >(ostream)), padWidth, ' ');
}
return ostream;
}

private:

/* static void fillPaddingBuffer(unsigned int size)
{
if (paddingBuffer_.size() < size)
{
paddingBuffer_.insert(paddingBuffer_.end(), size - paddingBuffer_.size(), ' ');
}
}*/


std::string value_;
int width_;
int align_;

// static std::vector< char > paddingBuffer_;

};

//std::vector< char > _s::paddingBuffer_;

template < typename TYPE >
std::basic_ostream< TYPE > & operator<<(std::basic_ostream< TYPE > & ostream, _d const & d)
{
return d.print(ostream);
}

template < typename TYPE >
std::basic_ostream< TYPE > & operator<<(std::basic_ostream< TYPE > & ostream, _s const & s)
{
return s.print(ostream);
}

int main()
{
int number = 123456789;
std::string string = "little";
std::cout << "Here is a number-" << _d(number, 4) << "-and a-" << _s(string, 10) << "-word\n";
number = -5;
std::cout << "Here is a number-" << _d(number, 4, left) << "-and a-" << _s(string, 10, left) << "-word\n";
number = 5;
std::cout << "Here is a number-" << _d(number, 4, right) << "-and a-" << _s(string, 10, left) << "-word\n";
}



Obviously this tramples on the implementor's names (but then so does boost with it's _1 etc.) and ignores formatting flags on the stream and you'd have to give up positional parameters, but apart from that, shouldn't this approach give the best of all worlds? Efficient (minimal dynamic memory allocation), concise (except for spelling out "left" and "right" and adding commas. '_l' and '_r' perhaps?), easy to use (no need to manage memory yourself), length safe, type safe, doesn't separate formatting information from the actual object being formatted and (with the obvious extension of making _d and _s template functions which return templated functors and adding a generic functor which doesn't convert its argument to the specified type) useable in templates.

Enigma

EDIT: a correction (I meant to say streams in general, not stringstreams in the first paragraph) and an improvement to the code (I always forget the fill_n is a part of the SC++L. Just because copy_n is non-standard I always think that fill_n is as well).

[Edited by - Enigma on September 5, 2005 7:41:36 AM]

Share this post


Link to post
Share on other sites
Quote:
Original post by Enigma
Obviously this tramples on the implementor's names (but then so does boost with it's _1 etc.)


WRONG, boost does NOT trample implementor names with _1 and the like in Boost.Lambda. Identifiers starting with a double underscore (or even containing for "__"), an underscore followed by an uppercase letter in any scope, or a lowercase letter in the file scope, are reserved for the implementation. However, boost::_1 does not fit this pattern, it's identifier is an underscore followed by a numerical digit. Ergo it's very poor reasoning to use such an excuse as "well, they did it too!" when they in fact did not do it too. Besides, two wrongs does not make a right.

Share this post


Link to post
Share on other sites
Quote:
Original post by MaulingMonkey
Quote:
Original post by Enigma
Obviously this tramples on the implementor's names (but then so does boost with it's _1 etc.)


WRONG, boost does NOT trample implementor names with _1 and the like in Boost.Lambda. Identifiers starting with a double underscore (or even containing for "__"), an underscore followed by an uppercase letter in any scope, or a lowercase letter in the file scope, are reserved for the implementation. However, boost::_1 does not fit this pattern, it's identifier is an underscore followed by a numerical digit. Ergo it's very poor reasoning to use such an excuse as "well, they did it too!" when they in fact did not do it too. Besides, two wrongs does not make a right.

Ah. my mistake. I simplified the rule in my head to "all identifiers starting with an underscore or containing a double underscore are reservered for the implementation". I only used _d and _s because it was intended as an example of an idealised system (in which such names would be legal [grin]). So just replace _d and _s with d_ and s_ respectively.

Enigma

Share this post


Link to post
Share on other sites
Well the underscores were my attempt at very short functor names without unnecessary name trampling (or making the whole thing redundant by requiring a namespace prefix). If you just used d and s then eventually you're going to want to support i and Wham! there goes a very common (but bad IMHO) loop variable name. It's a question of practicality.

But regardless of the names used, I'd be more interested in knowing what people think of the concept as a whole. Or does there exist a suitable library which all the qualities listed above (as far as I understand it, boost::format fails on efficiency and partially lacks static typing - by which I mean that the statement boost::format("%d") % std::string("test") will do the wrong thing at runtime rather than failing at compile time as my proposed system could be made to do. It also separates formatting information from the formatted object, which can be a positive or a negative depending on how you look at it. Don't get me wrong, it's a great library, but I want to have my cake and eat it!)

I feel I ought to make explicit that I consider there to be two types of formatting - known static formatting, in which you always want things formatted an exact way, and potentially dynamic formatting where, for example, you may want the format to change at runtime for localisation purposes. My proposal is a solution to the first, boost::format is a solution to both, but lacks efficiency in the first case simply because it is a more general solution.

Enigma

Share this post


Link to post
Share on other sites
Use boost::format - because if you can't tell for yourself if it's slow or not yourself, it's probably not a performance bottleneck, definately isn't confirmed as one, and is thus a waste of time to be worried about optimizing through the nanoseconds. Especially if your message is only displayed once in the entire program.

I think I've only seen someone complaining about boost::format's performance with an actual example case once on these forums (to format an FPS counter), and if my memory serves, storing the boost::format object so he wasn't reconstructing it hundreds of times a second solved his problem.

EDIT: preformance performance. Grr.

[Edited by - MaulingMonkey on September 5, 2005 3:51:18 PM]

Share this post


Link to post
Share on other sites
Quote:
Original post by SiCrane
Because it was written four years ago.


So boost::lexical_cast existed 4 years ago, but format didn't?

Share this post


Link to post
Share on other sites
Quote:
Original post by Promit
Quote:
Original post by SiCrane
Because it was written four years ago.


So boost::lexical_cast existed 4 years ago, but format didn't?


Quote:
From Boost.Format's documentation
© Samuel Krempp 2002


(2002 = ~3 years ago)

Quote:
From Boost.Conversion's lexical_cast documetation
© Copyright Kevlin Henney, 2000–2005


(2000 = ~5 years ago)

So I'd say, "probably, yes". The Boost Library Collection is an iterative, ongoing work, after all.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this