Boost logo

Boost :

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


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk