Pattern for writing a generic string transformation function I often need to write string processing functions that take an input string and transform that into some output string. I'm looking for a pattern to make such functions as generic as possible (with reasonable effort).

Requirements


It should be possible to supply the input as a range (like std::string or std::string_view), pair of iterators, null-terminated character arrays and pointers.
Character type should not be restricted to char, but also support wchar_t. Support char16_t and char32_t only if it doesn't require additional effort as I currently have no use for them.
Easy to use for the simple case where input and output is a std::basic_string.
Easy to write and more importantly easy to read.
Performance is less important. Processing character-by-character is good enough.


I currently approach this by splitting the interface into two template functions. The first one is the idiomatic generic interface which is iterator based, the second one is range based for convenience. The convenience interface is allowed to trade some generalism for ease-of-use.

Example

Escape all occurences of the characters '\n', '\t' and '\\' in the input string.

First the idiomatic interface.

template&lt; class InputIt, class OutputIt &gt;
OutputIt EscapeString( InputIt first, InputIt last, OutputIt out )
{
    while( first != last )
    {
        auto c = *first++;
        switch( c )
        {
            case '\n':
                *out++ = '\\';
                *out++ = 'n';
                break;
            case '\t':
                *out++ = '\\';
                *out++ = 't';
                break;
            case '\\':
                *out++ = '\\';
                *out++ = '\\';
                break;
            default:
                *out++ = c;
        }
    }
    return out;
}


Now the convenience wrapper. InputString can be any string range (like std::string), null-terminated char array or pointer to null-terminated string.
The result will be std::basic_string&lt;character_type_of_InputString&gt; by default, but can be changed to any class that has a push_back() member. 

template&lt; typename InputString, 
          class ResultString = typename as_literal_traits&lt;InputString&gt;::std_string_type &gt;
ResultString EscapeStringCopy( const InputString&amp; input )
{
    // Allow both the ADL-selected overloads for user-defined types and the standard 
    // library function templates to appear in the same overload set.
    using std::begin; using std::end;

    // Handle null-terminated character arrays, character pointers and ranges uniformly. 
    auto inputRange = boost::as_literal( input );

    ResultString result;
    EscapeString( begin( inputRange ), end( inputRange ), back_inserter( result ) );
    return result;
}


The template as_literal_traits is an utility that is used to create a default return type of std::basic_string that has the same character type as the input string.

template&lt; typename StringT &gt;
struct as_literal_traits
{
    // NOTE: std::declval&lt;StringT const&amp;&gt;() is used instead of just StringT{} 
    // in case StringT is not default-constructible.
    using range_type = decltype( boost::as_literal( std::declval&lt;StringT const&amp;&gt;() ) );
    using value_type = typename boost::range_value&lt; range_type &gt;::type;
    using std_string_type = std::basic_string&lt; value_type &gt;;
};


Live demo at Coliru

Any improvements are welcome.
Look at Boost.Range.  Your first point drives most of the rest of the concerns.

Write your template function to take a Range.  You can pass it a C string literal, a std::string, an array, vector, or anything —— even a pair of iterators if that’s what you need after all.

Not limited to char?  No problem: make it a Range of T, with a default of char.

This takes the place of your initial idiomatic approach.  Use ranges rather than iterators, and you don’t need to wrap it for convenience.  Boost.Range does all that for you.

The output can remain an output iterator, but I like to make it a range adaptor.

With that, you can do things like:

for (auto c : mystring|escaped) { ⋯