Print comma separated list from std::vector
One way of solving this I have seen is:
std::string separator;
for (auto x : vec) {
ss << separator << x.specific_detail;
separator = ",";
}
A fairly easy and reusable way:
#include <vector>
#include <iostream>
template<class Stream, class T, class A>
Stream& printem(Stream&os, std::vector<T, A> const& v)
{
auto emit = [&os, need_comma = false](T const& x) mutable
{
if (need_comma) os << ", ";
os << x;
need_comma = true;
};
for(T const& x : v) emit(x);
return os;
}
int main()
{
auto v = std::vector<int> { 1, 2, 3, 4 , 5 };
printem(std::cout, v) << std::endl;
}
And another way which defines an extendable protocol for printing containers:
#include <vector>
#include <iostream>
template<class Container>
struct container_printer;
// specialise for a class of container
template<class T, class A>
struct container_printer<std::vector<T, A>>
{
using container_type = std::vector<T, A>;
container_printer(container_type const& c) : c(c) {}
std::ostream& operator()(std::ostream& os) const
{
const char* sep = "";
for (const T& x : c) {
os << sep << x;
sep = ", ";
}
return os;
}
friend std::ostream& operator<<(std::ostream& os, container_printer const& cp)
{
return cp(os);
}
container_type c;
};
template<class Container>
auto print_container(Container&& c)
{
using container_type = typename std::decay<Container>::type;
return container_printer<container_type>(c);
}
int main()
{
auto v = std::vector<int> { 1, 2, 3, 4 , 5 };
std::cout << print_container(v) << std::endl;
}
...of course we can go further...
#include <vector>
#include <iostream>
template<class...Stuff>
struct container_printer;
// specialise for a class of container
template<class T, class A, class Separator, class Gap, class Prefix, class Postfix>
struct container_printer<std::vector<T, A>, Separator, Gap, Prefix, Postfix>
{
using container_type = std::vector<T, A>;
container_printer(container_type const& c, Separator sep, Gap gap, Prefix prefix, Postfix postfix)
: c(c)
, separator(sep)
, gap(gap)
, prefix(prefix)
, postfix(postfix) {}
std::ostream& operator()(std::ostream& os) const
{
Separator sep = gap;
os << prefix;
for (const T& x : c) {
os << sep << x;
sep = separator;
}
return os << gap << postfix;
}
friend std::ostream& operator<<(std::ostream& os, container_printer const& cp)
{
return cp(os);
}
container_type c;
Separator separator;
Gap gap;
Prefix prefix;
Postfix postfix;
};
template<class Container, class Sep = char, class Gap = Sep, class Prefix = char, class Postfix = char>
auto print_container(Container&& c, Sep sep = ',', Gap gap = ' ', Prefix prefix = '[', Postfix postfix = ']')
{
using container_type = typename std::decay<Container>::type;
return container_printer<container_type, Sep, Gap, Prefix, Postfix>(c, sep, gap, prefix, postfix);
}
int main()
{
auto v = std::vector<int> { 1, 2, 3, 4 , 5 };
// json-style
std::cout << print_container(v) << std::endl;
// custom
std::cout << print_container(v, " : ", " ", "(", ")") << std::endl;
// custom
std::cout << print_container(v, "-", "", ">>>", "<<<") << std::endl;
}
expected output:
[ 1,2,3,4,5 ]
( 1 : 2 : 3 : 4 : 5 )
>>>1-2-3-4-5<<<
Here is a tiny simple range library:
template<class It>
struct range_t {
It b, e;
It begin() const { return b; }
It end() const { return e; }
bool empty() const { return begin()==end(); }
std::size_t size() const { return std::distance( begin(), end() ); }
range_t without_front( std::size_t n = 1 ) const {
n = (std::min)(size(), n);
return {std::next(b, n), e};
}
range_t without_back( std::size_t n = 1 ) const {
n = (std::min)(size(), n);
return {b, std::prev(e, n)};
}
range_t only_front( std::size_t n = 1 ) const {
n = (std::min)(size(), n);
return {b, std::next(b, n)};
}
range_t only_back( std::size_t n = 1 ) const {
n = (std::min)(size(), n);
return {std::prev(end(), n), end()};
}
};
template<class It>
range_t<It> range(It s, It f) { return {s,f}; }
template<class C>
auto range(C&& c) {
using std::begin; using std::end;
return range( begin(c), end(c) );
}
now we are ready.
auto r = range(vec);
for (auto& front: r.only_front()) {
std::cout << front.x;
}
for (auto& rest: r.without_front()) {
std::cout << "," << rest.x;
}
Live example.
Now you can get fancier. boost transform iterators, together with boost range, let you do something similar to a list comprehension in python. Or Rangesv3 library for C++2a.
Writing a transform input iterator isn't amazingly hard, it is just a bunch of boilerplate. Simply look at the axioms of input iterator, write a type that stores an arbitrary iterator and forwards most methods to it.
It also stores some function. On *
and ->
, call the function on the dereferenced iterator.
template<class It, class F>
struct transform_iterator_t {
using reference=std::result_of_t<F const&(typename std::iterator_traits<It>::reference)>;
using value_type=reference;
using difference_type=std::ptrdiff_t;
using pointer=value_type*;
using iterator_category=std::input_iterator_tag;
using self=transform_iterator_t;
It it;
F f;
friend bool operator!=( self const& lhs, self const& rhs ) {
return lhs.it != rhs.it;
}
friend bool operator==( self const& lhs, self const& rhs ) {
return !(lhs!=rhs);
}
self& operator++() {
++it;
return *this;
}
self operator++(int) {
auto r = *this;
++*this;
return r;
}
reference operator*() const {
return f(*it);
}
pointer operator->() const {
// dangerous
return std::addressof( **this );
}
};
template<class F>
auto iterator_transformer( F&& f ) {
return [f=std::forward<F>(f)](auto it){
return transform_iterator_t<decltype(it), std::decay_t<decltype(f)>>{
std::move(it), f
};
};
}
template<class F>
auto range_transfromer( F&& f ) {
auto t = iterator_transformer(std::forward<F>(f));
return [t=std::move(t)](auto&&...args){
auto tmp = range( decltype(args)(args)... );
return range( t(tmp.begin()), t(tmp.end()) );
};
}
Live example of transformer.
And if we add --
we can even use ostream iterator.
Note that std::prev
requires a bidirectional iterator, which requires forward iterator concept, which requires that the transform iterator return an actual reference, which is a pain.