Subject: Re: [boost] Variadic append for std::string
From: Christof Donat (cd_at_[hidden])
Date: 2017-01-19 08:35:39


Hi,

Am 18.01.2017 17:30, schrieb Richard Hodges:
> Totally agree with returning a string factory. That makes perfect
> sense.
> onto(x) could return the correct kind of wrapper, depending on the
> argument
> type of x. So it could cope with x being for example, std::string&,
> std::string const&, std::string&& or std::ostream&.

How about this:

auto s = concat(1, " ", 2).str(); // -> s = "1 2"
concat(" ", 3).append_to(s); // -> s = "1 2 3"
// reuse preallocated memory
concat(4, " ", 5).overwrite(s); // -> s = "4 5"

overwrite() could also take a std::string_view with C++17, or const
char* and size_t with earlier versions of the standard library.

> As an observation, expressing the join as an iterator pair lends itself
> to
> being implemented in terms of std::copy(first, last,
> formatting_iterator<...>).

That definatelly is a possible implementation, yes. Though, of course
having join(), concat() and maybe other functions return string
factories instead of strings, enables generating the whole string in a
single buffer without copying.

concat(join(...), " ", 42, " - ", join(...), format("format string %1%
%2% %3%", a, b, 42)).str();

concat() will allocate a long enough string and call overwrite() on the
results of join() and format() with string views on that string.

Taking everything together, a string factory will have at least
interface like this:

template<typename T>
constexpr bool string_factory() {
     return requires(T a, std::string& s, std::string_view v, const char*
p, size_t len) {
         // estimate necessary memory to render the string
         { a.size() } const -> size_t;

         // render and return result
         { a.str() } const -> std::string;

         // render at the end of an existing string - return the number
of generated chars
         { a.append_to(s) } -> size_t;

         // render into an existing string, reusing its preallocated
memory
         { a.overwrite(s) } -> size_t;

         // render into a string view
         { a.overwrite(v) } -> size_t;

         // render into a character buffer
         { a.overwrite(p, len) } -> size_t;
     };
}

I hope, the constraint syntax is more or less correct. I haven't used
constraints in real code up to now ;-)

> I think this is good for containers, but for a series of disjoint
> types, or
> for joining words (as opposed to letters), you'd still need some
> templatery.

Yes, sure. You might also want to have somthing like this:

std::tuple<std::string, int, double> my_tuple{"asdf", 42, 2.5;
join(separator(", "), my_tuple);
// -> "asdf, 42, 2.5"

I think, using C++17s std::apply() it should be a straight forward
wrapper around concat().

> boost::range springs to mind as a reasonable helper for expressing to
> concat (or join) that you want to treat each element of a container.

Yes, when I wrote about my idea of join(), I thought of ranges as well.
With range adaptors, that will make up for a very powerfull and btw.
fast library to generate strings:

format("file names and sizes:\n%1%\n",
        join(separator('\n'),
             my_files |
             range::transformed([](const std::filesystem::path& f) ->
auto {
                                    return concat(separator(": "),
                                                  f.filename(),
                                                  
std::filesystem::file_size(f));
                                })).str();

format().str() will ask the join() string factory to render into the
preallocated buffer. Then join() will walk through its range and find,
that it has a range string factories, returned by concat(). Therefore it
asks every string factory to render to the given buffer.

We "just" need format(), join(), concat(), and the corresponding string
factories.

Christof