Is it possible to use Boost.Format with a preallocated buffer?
Asked Answered
I

1

7

I was wondering whether Boost.Format does support using a fixed-width / preallocated buffer as output instead of a dynamic buffer managed by the lib itself?

That is, normally you'd do:

boost::format myfmt("arg1: %1% / arg2: %2%");
// e.g.:
cout << (myfmt % 3.14 % 42);
// or
string s = boost::str( myfmt % "hey!" % "there!");

so the Boost:Format lib will automatically take care of allocating enough space and managing the "output buffer" for you.

I was wondering if there's any way to use a predefine non-dynamic buffer with Boost.Format, that is, something like:

const size_t buf_sz = 512;
char big_enough[buf_sz];
boost::format myfmt("arg1: %1% / arg2: %2%");
myfmt.attach_buffer(big_enough, buf_sz);
myfmt % "hey!" % "there!"
// big_enough buffer now contains the result string

I know I could just sift through the examples, the docs and the source, but apart from lacking time atm. (and the very possibility of missing something) it would be interesting to know: If it is not possible, it would be great if someone could explain why (if there is/are specific whys) -- was it deliberate? doesn't it match the API well? ...?

Disclaimer: This question is not about performance!

Incretion answered 2/11, 2011 at 13:33 Comment(3)
What do you want to happen when you run out of room? For a fixed buff I would use snprintf, but that's me :)Inge
@Inge If it doesn't fit, the library can/could either throw an exception or just stop filling the buffer (similar to the options already available)Incretion
I'm not sure that those exceptions apply to the target bufferInge
B
5

Initial Idea

Looking at the source it seems you can use your own allocator which is then used by the internal stream (internal_streambuf_t) of boost::format. Would that be good enough for your case?

For example you could use something like the libstdc++ array_allocator

Unfortunately boost::format also uses a couple of std::vector which do not use the custom allocator which may be a problem in your case?

How boost::format works

I looked into the source of boost::format and this is how it works (described below is str(), << calls either str() or uses standard std::ostream stuff) :

  • the format class stores all arguments and format string separatly, sometimes using the custom allocator, sometimes using the default allocator
  • when str() is called it creates a new std::string and makes it large enough for the result using the custom allocator
  • it then appends all arguments and static string pieces from the format string to the result string
  • finally it returns the result string by value

So, the final result string is not stored inside the format class but created when needed.

So even if you can find the location of the result string when using a custom allocator, it is only available after/during a call to str(). This should explain why it is not possible: the formatted result is never stored inside an "output buffer" in the class.

Why it works like this

Why they did it this way I do not know. I think it is because you can only build the result after all arguments are known, it wastes space to store the result and you probably only need the result just once for a given format/argument combination. So creating it when needed does not result in extra work since typically str() is only called once.

Solutions

  • Create some wrapper around str() or << and copy the result into your fixed buffer
  • Use a stream_buffer to 'stream' the string into the buffer (see example below)
  • Inherit the class and add your own str() function which stores the result in a fixed buffer.

Possible solution using boost::iostreams (tested):

#include <iostream>
#include <boost/format.hpp>
#include <boost/iostreams/stream.hpp>

int main()
{
    char buffer[100];

    boost::iostreams::stream<boost::iostreams::array_sink>
        stream(buffer, sizeof(buffer));

    stream << (boost::format("arg1 = %1%") % 12.5);
    stream << '\0';  // make sure buffer contains 0-terminated string

    std::cout << buffer << std::endl;    
}
Betake answered 3/11, 2011 at 7:52 Comment(4)
Useful info. I think a slight problem with allocators is that they are only allowed to report failure via bad_alloc, correct?Incretion
@Martin: I think you are right. Also, I looked into the source of format and I think using an allocator is difficult to do. I have updated my answer with the things I found.Betake
Nice job! Do I understand you correctly: The output string is re-built every time .str() is called?Incretion
@Martin: Yes, the output string is re-build every time str() is called.Betake

© 2022 - 2024 — McMap. All rights reserved.