Today I found myself reinventing output separators for the zillionth time.
I have converged on an approach like the following that seem pretty clean:
#include <iostream>
void foo(int a, int b) {
auto sep = [first=true]() mutable { return std::exchange(first, false); };
std::cout << "foo(";
if (a) std::cout << sep() << "a:" << a;
if (b) std::cout << sep() << "b:" << b;
std::cout << ")\n";
}
int main() {
foo(0,0);
foo(0,1);
foo(2,3);
foo(4,0);
}
Extracting Building Blocks?
This could be made a bit more general by extracting the mutating state:
template <typename T = bool, T initial = true, T fallback = false>
struct once {
mutable T flag = initial;
operator T() const { return std::exchange(flag, fallback); }
};
So that we can now write the sep() lambda as:
auto sep = [first=once{}]{ return first? "" : ", "; };
Abstracting The Whole One-Off Value
We can go one better using
auto sep = one_off("", ", ");
With:
template <typename T = char const*>
struct one_off {
mutable once<> flag;
T first, other;
one_off(T first, T other) : first(std::move(first)), other(std::move(other)) {}
operator T() const { return flag? first : other; }
};
Used as: Live On Coliru
void foo(int a, int b) {
auto sep = one_off("", ", ");
std::cout << "foo(";
if (a) std::cout << sep << "a:" << a;
if (b) std::cout << sep << "b:" << b;
std::cout << ")\n";
}
Reflection And Merit?
As you can see these are all potentially useful building blocks. I'm wondering whether anyone else uses similar building blocks and what they learned from actual usage.
Ideas:
what about a dynamic separator? It's much easier to capture, say, columnar formatting in a stateful lambda eg: Live On Coliru
void foo(std::vector<int> const& v) { auto sep = [n=0u]() mutable { if (n++) return n%5==1?"\n" : ", "; return ""; }; for (auto& el : v) std::cout << sep() << std::setw(5) << el; std::cout << "\n"; }Doesn't this conflict with standard library concepts like
std::once_flagfrom C++11 too much?Intuitively I feel these don't mix too well. Say, we want a
every_nthstyle value wrapper instead ofone_off(to replace every nth, instead of just the first)? Suppose we want to return from an input sequence, transforming every nth? Aren't we at risk of reinventing a Range Library?Conversely, is there a straight-forward way to express these building blocks using an existing (range) library such as Boost Range or RangeV3?
Notes:
I used C++17 for brevity of exposition; it's not about using C++17 (I'm aware of
ostream_joiner).It's not about string joining per se, which is why I chose a contrived example. In this example, obviously you could just print the delimiter
if (a && b)but that's not the point of the exercise.Thanks to @LucDanton for suggesting
std::exchange
1 Answer 1
That's fine as far as it goes.
Though I am not sure why you want all the added complexity for a problem that seems to be already solved.
What I think we all want is to stream containers in a logical way, that is consistent across containers.
Your basic example:
for(auto const& el: c) {
std::cout << sep() << el;
std::cout << "\n";
This is a specialization of how I would want containers to be stremed:
auto sep = one_off("", sepObject);
stream << beginMarker;
for(auto const& el: c) {
stream << sep() << el;
stream << endMarker;
So for example:
// An array looks like this:
[ 1, 2, 3, 4, 5, 6 ] // Note terminator is "]\n"
// A map look like this:
{ a => b, c => d, e => f }
So would it be not more logical to define a container streamer object.
std::Container c = {1,2,3,4,5};
ContainerStream(std::cout) << c;
Then you can use traits to define the beginMarker, endMarker and sepObject.
Or alternatively rather than wrapping the stream we could potentially wrap the container in something that knows how to print it (and generalize that).
std::cout << jsonExport(c);
-
\$\begingroup\$ You focused on only one example that was not the introducing sample (picked from the discussion ideas at the back). In your particular example, I agree (and that's what I do). However, I'm specifically looking for any "proven set", "established convention", real life experience with devising the primitive building blocks with which to elegantly define such manipulators. \$\endgroup\$sehe– sehe2018年01月17日 20:58:56 +00:00Commented Jan 17, 2018 at 20:58
-
\$\begingroup\$ Off-topic: it is convention to put the magic in the streamable, rather than in the stream, since the stream type can't always be controlled. In the case where aspects like
beginMarker,endMarkeretc. are desired to stick with the stream, they can be stored there usingios::xallocand friends \$\endgroup\$sehe– sehe2018年01月17日 20:59:42 +00:00Commented Jan 17, 2018 at 20:59
?" ":""in sep). Was that on purpose? \$\endgroup\$