Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Range based for loops on null terminated strings

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:

  1. The range is an array
  2. The range is a class with a callable begin and end method
  3. 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?)

like image 704
Motti Avatar asked Jan 23 '13 10:01

Motti


2 Answers

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.

like image 185
R. Martinho Fernandes Avatar answered Sep 26 '22 03:09

R. Martinho Fernandes


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) ) 
like image 42
Motti Avatar answered Sep 24 '22 03:09

Motti