Range based for loops on null terminated strings

坚强是说给别人听的谎言 提交于 2019-11-29 17:17:23

问题


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?)


回答1:


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.




回答2:


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.




回答3:


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!