I have no particular preference. Depending on the circumstances, I alternate with most of your examples.
Another option that I sometimes use is similar to your Range
example, but uses plain old iterator ranges:
template <typename Iter> void f(Iter first, Iter last);
which has the good property that it easily works with both strings in C style (and allows the callee to determine the length of the string in constant time), as well as std::string
.
If the templates are problematic (perhaps because I don't want the function to be defined in the header), I sometimes do the same thing, but using char*
as iterators:
void f(const char* first, const char* last);
Again, it can be trivially used with both C-strings and C ++ std::string
(as I recall, C ++ 03 does not explicitly require strings to be contiguous, but every implementation I know uses adjacent lines, and I believe C ++ 0x will explicitly require it).
Thus, these versions allow me to pass more information than the simple C-style const char*
parameter (which loses information about the length of the string and does not process embedded zeros) in addition to supporting types as the main string (and, possibly, any other class of strings that you can think of) in an idiomatic way.
The disadvantage is, of course, that you have an additional parameter.
Unfortunately, string handling is not the greatest strength of C ++, so I don't think that there is one βbestβ approach. But a couple of iterators is one of several approaches that I usually use.