I sort of assumed that range based for loops would support C-style strings
void print_C_str(const char* str)
{
for(char c : str)
{
cout << c;
}
}
However this is not the case, the standard [stmt.ranged] (6.5.4)
says that range-based-for works in one of 3 possibilities:
- The range is an array
- The range is a class with a callable
begin
andend
method - There is ADL reachable in an associated namespace (plus the
std
namespace)
When I add begin
and end
functions for const char*
in the global namespace I still get errors (from both VS12 and GCC 4.7).
Is there a way to get range-based-for loops to work with C style strings?
I tried adding an overload to namespace std
and this worked but to my understanding it's illegal to add overloads to namespace std
(is this correct?)
If you write a trivial iterator for null-terminated strings, you can do this by calling a function on the pointer that returns a special range, instead of treating the pointer itself as the range.
template <typename Char>
struct null_terminated_range_iterator {
public:
// make an end iterator
null_terminated_range_iterator() : ptr(nullptr) {}
// make a non-end iterator (well, unless you pass nullptr ;)
null_terminated_range_iterator(Char* ptr) : ptr(ptr) {}
// blah blah trivial iterator stuff that delegates to the ptr
bool operator==(null_terminated_range_iterator const& that) const {
// iterators are equal if they point to the same location
return ptr == that.ptr
// or if they are both end iterators
|| is_end() && that.is_end();
}
private:
bool is_end() {
// end iterators can be created by the default ctor
return !ptr
// or by advancing until a null character
|| !*ptr;
}
Char* ptr;
}
template <typename Char>
using null_terminated_range = boost::iterator_range<null_terminated_range_iterator<Char>>;
// ... or any other class that aggregates two iterators
// to provide them as begin() and end()
// turn a pointer into a null-terminated range
template <typename Char>
null_terminated_range<Char> null_terminated_string(Char* str) {
return null_terminated_range<Char>(str, {});
}
And usage looks like this:
for(char c : null_terminated_string(str))
{
cout << c;
}
I don't think this loses any expressiveness. Actually, I think this one is clearer.
A C-string is not an array, it is not a class that has begin
/end
members, and you won't find anything by ADL because the argument is a primitive. Arguably, this should be plain unqualified lookup, with ADL, which would find a function in the global namespace. But, given the wording, I'm thinking that it is not possible.
A possible workaround is to wrap the null terminated string in another type. The simplest implementation is as follows (it's less performant than R. Martinho Fernandes's suggestion since it calls strlen
but it's also significantly less code).
class null_terminated_range {
const char* p:
public:
null_terminated_range(const char* p) : p(p) {}
const char * begin() const { return p; }
const char * end() const { return p + strlen(p); }
};
Usage:
for(char c : null_terminated_range(str) )
来源:https://stackoverflow.com/questions/14477581/range-based-for-loops-on-null-terminated-strings