vsprintf-like function for strings?

May 24, 2014 at 10:18pm
I am trying to create a function like so:
1
2
3
4
5
6
7
8
void out(std::string format, ...)
{
va_list args;
va_start(args, format);
std::string formatted_string = v_format_string(format, args);
ostream_object << formatted_string;
va_end(args);
}

In the body of "v_format_string()", I allocate a certain amount of memory, store it in a std::unique_ptr<char[]> called 'p' by way of a call to 'p.reset(allocated_memory)', and then attempt to call:
 
n = vsnprintf(&p[0], allocated_size, format.c_str(), args);

and if 'n' is positive and less than 'allocated_size' the function allocates twice as much memory, storing it in 'p' with yet another call to 'reset()', and attempts another 'vsprintf()' call, repeating the process until enough memory is used; that's the plan. It seems, however, a segmentation fault occurs when I attempt to do so. Is my logic correct? Can a 'va_list' parameter not be referenced multiple times in the same function? More importantly, is there a vsprintf-like function for std::string's? Note: Since I am trying to get 'out()' to work similar to 'printf()', I cannot know the format strings ahead of time, if this fact makes any difference.

I appreciate Your help in advance.
Last edited on May 24, 2014 at 11:12pm
May 24, 2014 at 10:27pm
If you want to use a std::string somewhere that requires a c-string, you have to use .c_str()
May 24, 2014 at 10:33pm
@Yay295: Thanks for pointing this out. However, as noted, in the body of "v_format_string()", I do use that member function. Or are You referring to another use?
May 24, 2014 at 10:35pm
You could give boost::format a shot. It looks like this:
1
2
boost::format f("%1% Not even %2% type specifier!");
std::string s = (f % std::string("Hey!") % 1).str();
It also accepts printf()-like format strings.
May 24, 2014 at 10:38pm
@helios: Thanks for the idea but I don't see how this would work inside 'v_format_string()'.
May 24, 2014 at 10:40pm
@xuinkrbin: I did notice that. It just seemed like you were asking if there was another way to do it. There isn't as far as I know (which admittedly isn't that far), so I was just confirming that what you did was correct.
May 24, 2014 at 10:41pm
I think he was suggesting it as an alternative to using v_format_string().
May 24, 2014 at 10:45pm
Actually, I was suggesting it instead of out(). Variadic functions are rather passé.
May 24, 2014 at 10:50pm
Sorry, that's what I meant... I don't know what I was thinking :)
May 24, 2014 at 10:53pm
Could vsnprintf() be used?
May 24, 2014 at 11:11pm
@helios: I don't see how One concludes "Variadic functions are rather passé".

@Yay295: Actually, I do have that call in code. I have a typo in the original post which I will correct if I can.

@Everyone: What I have posted is a stripped down version of the actual function, which performs other actions including, but not limited to, outputting the resulting string to a second stream, if this fact makes any difference.
May 24, 2014 at 11:39pm
I don't see how One concludes "Variadic functions are rather passé".
If you were programming in C, you'd have no comfortable alternative to variadic functions for passing an indeterminate amount of parameters of indeterminate type to an object. With more expressive power of C++ (function overloading, operator overloading, templates, etc. etc.) variadic functions are a step backwards in almost every situation.
Last edited on May 24, 2014 at 11:44pm
May 24, 2014 at 11:53pm
@helios: Interesting explanation. In this particular project, I have literally over a thousand calls to 'printf()' which need to be replaced with this new function. In order to ease transition to the use of this new function, the hope was a drop-in replacement could be crafted. Do You have any additional insights to help in this regard?
May 25, 2014 at 1:03am
Well, as a zero-effort drop-in replacement, you could do something like #defineing printf to refer to an object of your own that overloads the comma operator. That should be possible with variadic macros.
May 25, 2014 at 1:25am
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <cstdio>
#include <string>
#include <cstdarg>
#include <stdexcept>

std::string out( std::string format, ... )
{
    va_list args, args_copy ;
    va_start( args, format ) ;
    va_copy( args_copy, args ) ;

    const auto sz = std::vsnprintf( nullptr, 0, format.c_str(), args ) + 1 ;

    try
    {
        std::string result( sz, ' ' ) ;
        std::vsnprintf( &result.front(), sz, format.c_str(), args_copy ) ;

        va_end(args_copy) ;
        va_end(args) ;

        // do whatever else with result

        return result ;
    }

    catch( const std::bad_alloc& )
    {
        va_end(args_copy) ;
        va_end(args) ;
        throw ;
    }
}

http://coliru.stacked-crooked.com/a/511842875075aa26
May 25, 2014 at 1:56am
@JLBorges: YES!!! VA_COPY!!! I plunked that into 'v_format_string()' and all is well. ... At least as far as I can tell. ;-) Thank You!

@helios: I will definitely keep Your points in Mind going forward and see if I cannot nudge the project in the direction to which You have hinted/implied/suggested/whatever ...
May 25, 2014 at 5:30am
As far as possible, unless you are dealing with a toy code-base, avoid #define of well-known identifiers like printf().
And avoid overloading the sequencing (comma) operator; it engenders behaviour that is unintuitive.

A set of overloaded functions is all that is required.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>
#include <string>
#include <boost/format.hpp>

namespace impl
{
    namespace
    {
        boost::format& out( boost::format& fmt ) { return fmt ;}

        template < typename T >
        boost::format& out( boost::format& fmt, const T& v ) { return fmt % v ;}

        template < typename FIRST, typename... REST >
        boost::format& out( boost::format& fmt, const FIRST& first, const REST&... rest )
        { return out( out( fmt, first ), rest... ) ;}
    }
}

template < typename... ARGS > std::string out( std::string format, const ARGS&... args )
{
    boost::format fmt(format) ;
    impl::out( fmt, args... ) ;

    const std::string result = fmt.str() ;
    // do whatever else with result

    return result ;
}

int main()
{
    std::cout << out( "trivial\n" ) ;
    std::cout << out( "non-trivial: %+d %c %2.2f '%s'\n", 12345, 'A', 67.899, "hello" ) ;
}

http://coliru.stacked-crooked.com/a/5388f19b580b66a3

Caveat: With boost format, 100% fidelity to printf() is compromised.
http://www.boost.org/doc/libs/1_55_0/libs/format/doc/format.html#printf_differences
May 27, 2014 at 2:34am
Adding to what JLBorges says about macro's and 'printf', defining 'printf' (or any other identifier in the standard library or any other reserved identifier) to be a macro results in undefined behavior according to the ISO C/C++ standards. As such, I might advise NEVER defining a macro with such a name.
Topic archived. No new replies allowed.