6
\$\begingroup\$

I have the following two classes to make appending strings - and wstrings respectively - easier:

#include <string>
#include <sstream>

class StringBuilder
{
public:
    template <typename T>
    StringBuilder& operator<<(const T& t)
    {
        this->stream << t;
        return *this;
    }

    operator std::string() const;

private:
    std::ostringstream stream;
};

StringBuilder::operator std::string() const
{
    return this->stream.str();
}

class WStringBuilder
{
public:
    template <typename T>
    WStringBuilder& operator<<(const T& t)
    {
        this->stream << t;
        return *this;
    }

    explicit operator std::wstring() const;

private:
    std::wstringstream stream;
};

WStringBuilder::operator std::wstring() const 
{
    return this->stream.str();
}

Now, as you see, pretty much all of the code (except for the concrete string and stringstream types) is repeated, especially the operators.

The question is:

  • Is there any way to reduce the duplicated code? I thought about a base class, templates and specialization, but the problem is I don't know how to determine the correct stringstream type depending on the string type then.
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Small side point -- don't use wstring. It's a bad attempt at Unicode support, and should be phased out wherever possible. If you have to communicate with an API that takes it, convert it there. See utf8everywhere.org \$\endgroup\$
    – anon
    Feb 11, 2018 at 6:54

1 Answer 1

13
\$\begingroup\$

Yes, there is. In fact, a very nice way to solve this code redundancy issue comes to mind if you consider that std::string is a just type alias for std::basic_string<char> and std::wstring an alias for std::basic_string<wchar_t>. Likewise, std::stringstream and std::wstringstream are also just aliases for std::basic_stringstream<char> and std::basic_stringstream<wchar_t>, respectively. This allows for an easy template-based approach to the problem.

What really suggests itself to me is keeping this alias-of-template-base-type pattern from the STL (although there may be other valid approaches as well), an implementation of which could look something like this:

#include <sstream>
#include <string>

namespace impl {

    template <typename CharType>
    class StringBuilderImpl {

        std::basic_stringstream<CharType> stream;

        public:
        template <typename T>
        StringBuilderImpl& operator<<(T const& t) {
            stream << t;
            return *this;
        }

        operator std::basic_string<CharType>() const {
            return stream.str();
        }
    };
}

using StringBuilder = impl::StringBuilderImpl<char>;
using WStringBuilder = impl::StringBuilderImpl<wchar_t>;

StringBuilderImpl corresponds somewhat to std::basic_[string/stringstream] and takes the underlying character type as a template argument. Your former classes StringBuilder and WstringBuilder are now only aliases for certain template specializations, just as std::[string/wstring] and std::[stringstream/wstringstream] are.

(In my code, StringBuilderImpl is kind of hidden away inside an impl namespace. If it serves your need, however, you could also make it part of the public interface of your program library, allowing further specifications such as StringBuilderImpl<char16_t>)

\$\endgroup\$
1
  • \$\begingroup\$ Thank you very much, this is exactly what I was looking / hoping for. Awesome. \$\endgroup\$
    – user143162
    Feb 10, 2018 at 16:26

Your Answer

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