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_flag
from C++11 too much?Intuitively I feel these don't mix too well. Say, we want a
every_nth
style 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
?" ":""
in sep). Was that on purpose? \$\endgroup\$ – Zeta Mar 19 '18 at 7:03