Search code examples
c++iostreamstl-algorithm

std::ostream_iterator prevent last item from using the delimiter


Is there a way to use a std::ostream_iterator (or similar) such that the delimiter isn't placed for the last element?

#include <iterator>
#include <vector>
#include <algorithm>
#include <string>


using namespace std;
int main(int argc, char *argv[]) {
    std::vector<int> ints = {10,20,30,40,50,60,70,80,90};
    std::copy(ints.begin(),ints.end(),std::ostream_iterator<int>(std::cout, ","));
}

Will print

10,20,30,40,50,60,70,80,90,

I'm trying to avoid the trailing the delimiter. I want to print

10,20,30,40,50,60,70,80,90

Sure, you could use a loop:

for(auto it = ints.begin(); it != ints.end(); it++){
  std::cout << *it;
  if((it + 1) != ints.end()){           
    std::cout << ",";
  }
}

But given C++11 range based loops this is cumbersome to track position.

int count = ints.size();
for(const auto& i : ints){
  std::cout << i;
  if(--count != 0){
    std::cout << ",";
  }     
}

I'm open to using Boost. I looked into boost::algorithm::join() but needed to make a copy of the ints to strings so it was a two-liner.

std::vector<std::string> strs;
boost::copy(ints | boost::adaptors::transformed([](const int&i){return boost::lexical_cast<std::string>(i);}),std::back_inserter(strs));
std::cout << boost::algorithm::join(strs,",");

Ideally I'd just like to use a std::algorithm and not have the delimiter on the last item in the range.

Thanks!


Solution

  • @Cubbi pointed out in a comment that is is exactly what infix_iterator does

    // infix_iterator.h 
    // 
    // Lifted from Jerry Coffin's 's prefix_ostream_iterator 
    #if !defined(INFIX_ITERATOR_H_) 
    #define  INFIX_ITERATOR_H_ 
    #include <ostream> 
    #include <iterator> 
    template <class T, 
              class charT=char, 
              class traits=std::char_traits<charT> > 
    class infix_ostream_iterator : 
        public std::iterator<std::output_iterator_tag,void,void,void,void> 
    { 
        std::basic_ostream<charT,traits> *os; 
        charT const* delimiter; 
        bool first_elem; 
    public: 
        typedef charT char_type; 
        typedef traits traits_type; 
        typedef std::basic_ostream<charT,traits> ostream_type; 
        infix_ostream_iterator(ostream_type& s) 
            : os(&s),delimiter(0), first_elem(true) 
        {} 
        infix_ostream_iterator(ostream_type& s, charT const *d) 
            : os(&s),delimiter(d), first_elem(true) 
        {} 
        infix_ostream_iterator<T,charT,traits>& operator=(T const &item) 
        { 
            // Here's the only real change from ostream_iterator: 
            // Normally, the '*os << item;' would come before the 'if'. 
            if (!first_elem && delimiter != 0) 
                *os << delimiter; 
            *os << item; 
            first_elem = false; 
            return *this; 
        } 
        infix_ostream_iterator<T,charT,traits> &operator*() { 
            return *this; 
        } 
        infix_ostream_iterator<T,charT,traits> &operator++() { 
            return *this; 
        } 
        infix_ostream_iterator<T,charT,traits> &operator++(int) { 
            return *this; 
        } 
    };     
    #endif 
    
    #include <vector>
    #include <algorithm>
    #include <string>
    #include <iostream>
    
    using namespace std;
    int main(int argc, char *argv[]) {
        std::vector<int> ints = {10,20,30,40,50,60,70,80,90};
        std::copy(ints.begin(),ints.end(),infix_ostream_iterator<int>(std::cout,","));
    }
    

    Prints:

    10,20,30,40,50,60,70,80,90