Printing hex dumps for diagnostics
Did you ever test it with buffers which are multiples of 8 long?
Because the last block won't get the nice printable display.
Are you sure the newline at the beginning of the output should be there?
Should the test-output really contain the implicit 0-terminator?
return 0; is implicit for main().


Modified test-function:

int main()
{
    const char test[] = "abcdef123456\0zyxwvu987654";
    std::string s(test, sizeof(test) - 1);

    std::cout &lt;&lt; "---\n";
    hex_dump(std::cout, s);
    std::cout &lt;&lt; "---\n";
    hex_dump(std::cout, "");
    std::cout &lt;&lt; "---\n";
    hex_dump(std::cout, "12345678");
    std::cout &lt;&lt; "---\n";
    hex_dump(std::cout, "1234567812345678");
    std::cout &lt;&lt; "---\n";
}



Do you have the opening brace for functions on its own line or not? Seems 2+1 for, 1 against...
If you only want to output a single character, using a character is potentially more efficient than a string.
The conditional operator is excellent for choosing between two values.
Beware of the implementation-defined signedness of plain char. The character-classification-functions inherited from C expect the value of an unsigned char or EOF.
See std::isprint.
Consider marking internal functions static to avoid externally visible symbols and promote inlining.
If you don't actually ever use a return-value, why provide one?


static void render_printable_chars(std::ostream&amp; os, const char* buffer, size_t bufsize)
{
    os &lt;&lt; " | ";
    for (size_t i = 0; i &lt; bufsize; ++i)
        os &lt;&lt; (std::isprint((unsigned char)buffer[i]) ? buffer[i] : '.');
}



Don't play around with a useless flag just because. Tracing data-flow is quite a lot more involved than following the program-flow.
Testing whether a variable already has a value before setting it to the same is rarely anything but obfuscation and pessimisation.
Don't use std::endl unless you really want to flush. And if that's your intention, consider being more explicit by using std::flush.
If you use C++17, you might want to accept a std::string_view by value instead of a std::string by constant reference. It's generally more efficient.