Sometimes I need to pass a C string to a function using the common C++ iterator range interface [first, last)
. Is there a standard C++ iterator class for those cases, or a standard way of doing it without having to copy the string or call strlen()
?
EDIT:
I know I can use a pointer as an iterator, but I would have to know where the string ends, what would require me to call strlen()
.
EDIT2: While I didn't know if such iterator is standardized, I certainly know it is possible. Responding to the sarcastic answers and comments, this is the stub (incomplete, untested):
class CStringIterator
{
public:
CStringIterator(char *str=nullptr):
ptr(str)
{}
bool operator==(const CStringIterator& other) const
{
if(other.ptr) {
return ptr == other.ptr;
} else {
return !*ptr;
}
}
/* ... operator++ and other iterator stuff */
private:
char *ptr;
};
EDIT3: Specifically, I am interested in a forward iterator, because I want to avoid to iterate over the sring twice, when I know the algorithm will only have to do it once.
There isn't any explicit iterator class, but regular raw pointers are valid iterators as well. Problem with C-strings, though, is that they do not come with a native end iterator, which makes them unusable in range based for loops – directly at least...
You might like to try the following template, though:
template <typename T>
class Range
{
T* b;
public:
class Sentinel
{
friend class Range;
Sentinel() { }
friend bool operator!=(T* t, Sentinel) { return *t; }
public:
Sentinel(Sentinel const& o) { }
};
Range(T* begin)
: b(begin)
{ }
T* begin() { return b; }
Sentinel end() { return Sentinel(); }
};
Usage:
for(auto c : Range<char const>("hello world"))
{
std::cout << c << std::endl;
}
It originally was designed to iterate over null-terminated argv of main, but works with any pointer to null terminated array – which a C-string is as well...
Secret is comparing against the sentinel, which actually does a totally different comparison (current pointer pointing the terminating null (pointer))...
Edit: Pre-C++17 variant:
template <typename T>
class Range
{
T* b;
public:
class Wrapper
{
friend class Range;
T* t;
Wrapper(T* t) : t(t) { }
public:
Wrapper(Wrapper const& o) : t(o.t) { }
Wrapper operator++() { ++t; return *this; }
bool operator!=(Wrapper const& o) const { return *t; }
T operator*() { return *t; }
};
Range(T* begin)
: b(begin)
{ }
Wrapper begin() { return Wrapper(b); }
Wrapper end() { return Wrapper(nullptr); }
};
Actually, yes - sort of. In c++17.
C++17 introduces std::string_view
which can be constructed from a c-style string.
std::string_view
is a random access (proxy) container which of course fully supports iterators.
Note that although constructing a string_view from a const char*
will theoretically call std::strlen
, the compiler is allowed to (and gcc certainly does) elide the call when it knows the length of the string at compile time.
Example:
#include <string_view>
#include <iostream>
template<class Pointer>
struct pointer_span
{
using iterator = Pointer;
pointer_span(iterator first, std::size_t size)
: begin_(first)
, end_(first + size)
{
}
iterator begin() const { return begin_; }
iterator end() const { return end_; }
iterator begin_, end_;
};
int main(int argc, char** argv)
{
for(auto&& ztr : pointer_span(argv, argc))
{
const char* sep = "";
for (auto ch : std::string_view(ztr))
{
std::cout << sep << ch;
sep = " ";
}
std::cout << std::endl;
}
}
See the example output here
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With