4
\$\begingroup\$

The point of this is to concatenate a vector of std::strings into one string starting at an index of the vector.

std::string vectorSubstr(std::vector<std::string> vec, int startPos, char seperator)
{
    std::stringstream ss;

    for (int i = startPos; i < vec.size(); i++)
    {
        if (i == startPos)
        {
            ss << vec[i];
        }
        else
        {
            ss << seperator << vec[i];
        }
    }

    return ss.str();
}

Is there anything wrong with this approach? Should I be using a stringstream or just concatenating strings?

\$\endgroup\$
2
  • \$\begingroup\$ what does startPos signify? \$\endgroup\$ Commented May 29, 2015 at 3:35
  • \$\begingroup\$ @AbhinavGauniyal the index at which the function starts concatenating the vector. The reason for this is say I have a vector with some nonsense data at the beginning but the last few indexes have a message. I'd use this function in that case. \$\endgroup\$
    – user54490
    Commented May 29, 2015 at 12:07

3 Answers 3

2
\$\begingroup\$

std::[o]stringstream vs std::string::append()/std::string::operator+=()

With std::string::append(), you'll usually pay the initial allocation, and with each append you'll pay for a data copy and possibly an allocation. Constructing std::ostringstream is more complex compared to string. You have to initialize the stream, the states, and the locale. Every std::ostringstream::operator<<() call accrues a possible allocation, a data copy, and state checks. Once you've completed that, you have to invoke std::stringstream::str() to use the string, which involves a copy (depending on implementation).

With C++11 introducing non-member std::to_string() and std::stoXX() conversion functions and all the various format libraries out there, I haven't really found a use for std::ostringstream recently.

Explicit Typing

std::stringstream inherits from both std::istringstream and std::ostringstream. You'll find that rarely will you have to both read and write from a stream, so be explicit in your intent and use the std::[i|o]stringstream type.

Interface

vectorSubstr() could be confusing for some people. Does it return a std::vector<std::string> of substrings? Consider other names like to_string(), join(), concatenate().

Prefer writing range-based algorithms using the Iterator pattern that is heavily used by the STL.

If you do not plan on changing the contents of a parameter, const-qualify it.

Your parameter for the container is taken By-Value. Copies on containers (and non-primitives) can be expensive, so pass those arguments By-Reference (&).

std::string join(const std::vector<std::string> &input, const char separator)

Algorithm

You can reduce the amount of branching you have to do by handling the first element case, then your delimiter becomes naturally infixed.

Putting It Together

template <typename InputIterator, typename Separator>
inline typename std::iterator_traits<InputIterator>::value_type
join(InputIterator first, InputIterator last, const Separator& separator) {
  using ResultType = std::iterator_traits<InputIterator>::value_type;
  ResultType result;

  if (first != last) {
    result += *first;
    ++first;
  }

  for (; first != last; ++first) {
    result += separator;
    result += *first;
  }

  return result;
}

int main() {
  const std::vector<std::string> inputs = {"a", "bcd", "efghijklm", "nopqrstuvwxyz"};
  const auto first = std::next(std::begin(inputs), 2);
  const auto last = std::end(inputs);

  std::cout << join(first, last, ", ") << '\n';
}
\$\endgroup\$
2
\$\begingroup\$

If you are just trying to concatenate vector<string> elements, why not use the following approach:

std::string seperator = " ";
std::string vectorElementHolder;

for (const auto& word : vec){

    vectorElementHolder += word + seperator;

}

Simple and effective, assuming you use C++11. If you want to change the index where you start, you could always declare an iterator as such vector<string>::iterator to point to the index and go from there.

\$\endgroup\$
2
  • \$\begingroup\$ word should either be taken by const reference or it should be moved into += if a copy is made. Also this leaves a trailing separator. \$\endgroup\$
    – Corbin
    Commented May 31, 2015 at 3:23
  • \$\begingroup\$ Since word is not being changed, I agree it should be const reference. As to fix the trailing separator, vectorElementHolder.pop_back(). \$\endgroup\$
    – Ishaan
    Commented May 31, 2015 at 17:31
0
\$\begingroup\$

Is there anything wrong with this approach? Should I be using a stringstream or just concatenating strings?

I use a different approach: I have created a class similar to std::ostream_iterator (see the utility functions at the end of the code for example use):

#pragma once

#include <iosfwd>
#include <iterator>
#include <sstream>

namespace stdex {

    /**
     *  @brief  Implements join functionality directing the output into an ostream
     *
     *  This class provides an iterator for writing into an ostream. The type T is
     *  the only type written by this iterator and there must be an
     *  operator << (T) defined.
     * 
     *  @code
     * 
     * template<typename T> using join = ostream_join_iterator<T>;
     * std::ostringstream out;
     * std::vector<std::string> strings{"alpha", "beta", "gamma"};
     * std::copy(strings.begin(), strings.end(), join<string>{out, ", "});
     * assert(out.str() == "alpha, beta, gamma");
     * 
     *  @endcode
     * 
     *  @remarks This class implements functionality similar to
     *      std::ostream_iterator except it does not print a separator after the
     *      last element.
     *
     *  @param  T  The type to write to the ostream.
     *  @param  C  The ostream char_type.
     *  @param  Tr The ostream char_traits.
     */
    template<typename T, typename C=char, typename Tr=std::char_traits<C> >
    class ostream_join_iterator
    : public std::iterator<std::output_iterator_tag, void, void, void, void>
    {
    public:
        typedef C  char_type;
        typedef Tr traits_type;
        typedef std::basic_string<C, Tr>  splitter_type;
        typedef std::basic_ostream<C, Tr> ostream_type;

    private:
        ostream_type        *out_;
        splitter_type       splitter_;
        bool                at_first_;

    public:
        ostream_join_iterator(ostream_type& out)
        : out_{&out}
        , splitter_{}
        , at_first_{true}
        {
        }

        /**
         *  Construct from an ostream.
         *
         *  @remarks The delimiter is not copied, and thus must not be destroyed
         *          while this iterator is in use.
         *
         *  @param  out         Underlying ostream to write to.
         *  @param  splitter    CharT delimiter string to insert.
         */
        ostream_join_iterator(ostream_type& out, const char_type* splitter)
        : out_{&out}
        , splitter_{splitter}
        , at_first_{true}
        {
        }

        /// Copy constructor.

        ostream_join_iterator(const ostream_join_iterator& other)
        : out_{other.out_}
        , splitter_{other.splitter_}
        , at_first_{other.at_first_}
        {
        }

        ostream_join_iterator& operator=(const T& value)
        {
            if((not splitter_.empty()) and (not at_first_))
                *out_ << splitter_;
            *out_ << value;
            at_first_ = false;
            return *this;
        }

        ostream_join_iterator& operator=(ostream_join_iterator temp)
        {
            using std::swap;
            swap(out_, temp.out_);
            swap(splitter_, temp.splitter_);
            swap(at_first_, temp.at_first_);
            return *this;
        }

        ostream_join_iterator&
        operator*() {
            return *this;
        }

        ostream_join_iterator&
        operator++() {
            return *this;
        }

        ostream_join_iterator&
        operator++(int) {
            return *this;
        }
    };

    // join implementation with string separator

    template<typename It, typename C, typename Tr, typename A>
    void join(const It& begin, const It& end,
    const std::basic_string<C,Tr,A>& separator, std::basic_ostream<C,Tr>& out)
    {
        std::copy(begin, end,
            ostream_join_iterator<std::remove_reference<decltype(*begin)>>{out, separator});
    }

    template<typename It, typename C, typename Tr, typename A>
    std::basic_string<C,Tr,A> join(const It& begin, const It& end,
    const std::basic_string<C,Tr,A>& separator)
    {
        std::basic_ostringstream<C,Tr,A>  buffer{};
        join(begin, end, separator, buffer);
        return buffer.str();
    }

    template<typename S, typename C, typename Tr, typename A>
    void join(const S& sequence,
    const std::basic_string<C,Tr,A>& separator,
    std::basic_ostream<C,Tr>& out)
    {
        join(std::begin(sequence), std::end(sequence), separator, out);
    }

    template<typename S, typename C, typename Tr, typename A>
    std::basic_string<C,Tr,A> join(
    const S& sequence,
    const std::basic_string<C,Tr,A>& separator)
    {
        std::basic_ostringstream<C,Tr,A>  buffer{};
        join(std::begin(sequence), std::end(sequence), separator, buffer);
        return buffer.str();
    }

    // join implementation with character literal separator

    template<typename It>
    void join(const It& begin, const It& end,
    const char separator[], std::ostream& out)
    {
        typedef decltype(*begin) iterated;
        std::copy(begin, end, ostream_join_iterator<iterated>{out, separator});
    }

    template<typename It>
    std::string join(const It& begin, const It& end, const char separator[])
    {
        std::ostringstream  buffer{};
        join(begin, end, separator, buffer);
        return buffer.str();
    }
} // stdex
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.