Re: Generic C++ templates/library for FreeBSD base

From: John Baldwin <jhb_at_FreeBSD.org>
Date: Sat, 15 Mar 2025 17:41:47 UTC
On 3/14/25 10:59, Dimitry Andric wrote:
> On 14 Mar 2025, at 15:45, John Baldwin <jhb@FreeBSD.org> wrote:
>>
>> On 3/14/25 09:50, Dimitry Andric wrote:
>>> On 14 Mar 2025, at 14:36, John Baldwin <jhb@FreeBSD.org> wrozte:
>>>>
>>>> On 3/14/25 00:19, Gleb Popov wrote:
>>>>> On Thu, Mar 13, 2025 at 11:53 PM John Baldwin <jhb@freebsd.org> wrote:
>>>>>>
>>>>>> One is a stringf() function that accepts printf() style format string and
>>>>>> arguments and returns a std::string.  I know C++23 adds <format>, but we
>>>>>> can't assume that yet, and this function is probably more useful when
>>>>>> adapting existing C code.  Compared to some other solutions, I chose to
>>>>>> wrap asprintf() and do an extra copy at the end into a std::string rather
>>>>>> than calling vsnprintf() twice.  It seems less ugly than the vsnprintf()
>>>>>> solutions also:
>>>>>>
>>>>>> https://github.com/freebsd/freebsd-src/commit/01bd3d89ddf9ccbf884e52fe7289e8a9278e2d63
>>>>> I wonder why std::string always copies the data passed into the constructor.
>>>>> It is possible to avoid extra alloc by returning a std::string_view,
>>>>> but this would force the caller into freeing the memory manually.
>>>>> Maybe derive from it and extend the destructor, but this just brings
>>>>> me to the initial question.
>>>>
>>>> What you would want is something where you could do std::move() of the pointer
>>>> returned by asprintf(), into the std::string object, but that assumes that
>>>> new/delete are always using malloc/free (which is probably true in practice).
>>> The ideal solution would be to define an "inserter" that can be called
>>> from the guts of __vfprintf() (or wherever the "real" implementation of
>>> printf lives), whenever it wants to emit a character into the output
>>> buffer. And there you would simply do string::push_back(), which is
>>> amortized constant time.
>>
>> That you could do with funopen() where the write method called string::push_back().
>> This is actually how asprintf (and open_memstream) works internally.  Maybe I will
>> just fix my implementation to do that.
> 
> There is one fly in the ointment, however. The method of calling
> asprintf() and then putting the result into fresh std::string moves the
> possibility of exceptions to that point. But if you effectively call
> string::push_back() from within a C function, you have to put a
> try/catch block around it, and report an error in case you catch some
> exception.

Yes.  I have done the fwopen() version and indeed have chosen to catch exceptions
and return -1 from the the write handler, then back in stringf I throw std::bad_alloc
if ferror() is true.  Previously in the asprintf version I threw std::bad_alloc if
asprintf failed.  So about the same.  In terms of copies, it's probably about the
same as stdio uses an internal buffer that it pushes to the write callback, but
for asprintf, stdio has an optimization that reallocs the internal buffer and
returns it directly, so the funopen() version is probably the same number of
copies in the end as the asprintf version.

-- 
John Baldwin