Info
-
Did you know that C++20 added
ostream_joiner
that writes successive objects into the basic_ostream separated by a delimiter?
Example
int main() {
std::vector v{1, 2, 3};
std::copy(std::cbegin(v), std::cend(v), std::ostream_joiner{std::cout, ", "}); // prints 1, 2, 3
}
Puzzle
- Can you implement a simplified version of
std::ostream_joiner
?
/* TODO - ostream_joiner */
int main() {
using namespace boost::ut;
using std::literals::string_literals::operator""s;
"ostream joiner"_test = [] {
should("produce an empty") = [stream = std::stringstream{}, v = std::vector<int>{}] {
std::copy(std::cbegin(v), std::cend(v), ostream_joiner{mut(stream), ""});
expect(stream.str() == ""s);
};
should("join vector elements") = [stream = std::stringstream{}, v = std::vector{'a', 'b', 'c'}] {
std::copy(std::cbegin(v), std::cend(v), ostream_joiner{mut(stream), ""});
expect(stream.str() == "abc"s);
};
should("join vector elements with comma") = [stream = std::stringstream{}, v = std::vector{1, 2, 3}] {
std::copy(std::cbegin(v), std::cend(v), ostream_joiner{mut(stream), ", "});
expect(stream.str() == "1, 2, 3"s);
};
should("join array elements with pipe") = [stream = std::stringstream{}, v = std::array{"ab", "cd"}] {
std::copy(std::cbegin(v), std::cend(v), ostream_joiner{mut(stream), " | "});
expect(stream.str() == "ab | cd"s);
};
};
}
Solutions
struct ostream_joiner{
ostream_joiner(std::ostream& s, std::string_view separator)
: s{s}, separator{separator} {}
auto& operator++() { return *this; }
auto& operator*() { return *this; }
auto& operator=(auto&& anything) {
if (emit_separator)
s << separator;
else
emit_separator = true;
s << std::forward<decltype(anything)>(anything);
return *this;
}
private:
std::ostream& s;
const std::string_view separator;
bool emit_separator{false};
};
struct ostream_joiner {
ostream_joiner(std::stringstream& ss, std::string_view sep) : incr(false), ss(ss), sep(sep) {}
template<class T>
void operator=(T e) {
if(incr)
ss << sep;
incr = false;
ss << e; }
ostream_joiner& operator*() { return *this; }
void operator++() { incr = true; }
std::stringstream& ss;
std::string_view sep;
bool incr;
};
template <typename TStream, typename TSep>
struct ostream_joiner {
ostream_joiner(TStream& stream, TSep sep) : stream_(stream), sep_(std::move(sep)) {}
auto& operator*() { return *this; }
auto& operator=(const auto& c) {
if (not std::exchange(first_, false)) {
stream_ << sep_;
}
stream_ << c;
return *this;
}
auto& operator++() { return *this; }
private:
TStream& stream_;
TSep sep_;
bool first_ = true;
};
struct ostream_joiner {
private:
std::ostream& output;
const std::string delimiter;
bool first{true};
public:
ostream_joiner(std::ostream& stream, const std::string& delim) : output(stream), delimiter(delim) {}
template <typename T>
auto& operator=(T&& input)
{
if (first) {
first = false;
} else {
output << delimiter;
}
output << std::forward<T>(input);
return *this;
}
auto& operator++() { return *this; }
auto& operator*() { return *this; }
};
struct ostream_joiner {
constexpr explicit(true) ostream_joiner(auto& stream, const auto& sep)
: output_stream(stream)
, separator(sep)
{}
auto operator++() const noexcept { return *this; }
auto operator*() const noexcept{ return *this; }
auto operator=(const auto& rhs)
{
static bool is_first = true;
if (not is_first) {
output_stream << separator;
} else {
is_first = false;
}
output_stream << rhs;
return *this;
}
std::stringstream& output_stream;
std::string separator;
};