views:

376

answers:

4

(Note: I'm writing this project for learning only; comments about it being redundant are... uh, redundant. ;)

I'm trying to implement a random access iterator, but I've found very little literature on the subject, so I'm going by trial and error combined with Wikpedias list of operator overload prototypes. It's worked well enough so far, but I've hit a snag.

Code such as

exscape::string::iterator i = string_instance.begin();
std::cout << *i << std::endl;

works, and prints the first character of the string. However, *(i + 1) doesn't work, and neither does *(1 + i). My full implementation would obviously be a bit too much, but here's the gist of it:

namespace exscape {
    class string {
        friend class iterator;
    ...
    public:
        class iterator : public std::iterator<std::random_access_iterator_tag, char> {
            ...
            char &operator*(void) {
                return *p; // After some bounds checking
            }
            char *operator->(void) {
                return p;
            }

            char &operator[](const int offset) {
                return *(p + offset); // After some bounds checking
            }

            iterator &operator+=(const int offset) {
                p += offset;
                return *this;
            }

            const iterator operator+(const int offset) {
                iterator out (*this);
                out += offset;
                return out;
            }

        };
};
}

int main() {
    exscape::string s = "ABCDEF";
    exscape::string::iterator i = s.begin();
    std::cout << *(i + 2) << std::endl;
}

The above fails with (line 632 is, of course, the *(i + 2) line):

string.cpp: In function ‘int main()’: string.cpp:632: error: no match for ‘operator*’ in ‘*exscape::string::iterator::operator+(int)(2)’ string.cpp:105: note: candidates are: char& exscape::string::iterator::operator*()

*(2 + i) fails with:

string.cpp: In function ‘int main()’: string.cpp:632: error: no match for ‘operator+’ in ‘2 + i’ string.cpp:434: note: candidates are: exscape::string exscape::operator+(const char*, const exscape::string&)

My guess is that I need to do some more overloading, but I'm not sure what operator I'm missing.

+9  A: 

Your + operator returns a const iterator, but you don't have a const operator*. Add one, and I think you'll be all right. Or, as suggested by xtofl below, you can make your operator* const. That's better design, unless you really need the non-const operator* for some reason.

David Seiler
Or, given the code, make your `operator*` a `const` one.
xtofl
(And while you go, take `operator->` along ;)
xtofl
I may be mistaken here, but `operator-> returning char*`? Where would that be useful?
UncleBens
+4  A: 

To have it work with the numeric constant on the left you will need a non-member function. Something like this (untested code):

exscape::string::iterator operator+(exscape::string::iterator it, size_t n) {
    return it += n;
}

exscape::string::iterator operator+(size_t n, exscape::string::iterator it) {
    return it += n;
}
Evan Teran
You really shouldn't be modifying `it` though in operator+
cobbal
It doesn't have to be in the global namespace (unless I've missed something to do with iterator being a nested class). Approximately 50% of the purpose of ADL is to let you define non-member operators in the namespace of the class they apply to.
Steve Jessop
@cobbal: no, he's fine. The operator takes `it` as a copy of the argument, modifies the copy, and returns that modified value. What he's written is equivalent to `iterator retval = it; retval += n; ret retval;`, but somewhat more likely to benefit from copy elision.
Steve Jessop
I should have said, equivalent assuming that the copy constructor of the iterator doesn't do anything too exciting.
Steve Jessop
Oh yeah, I've been away from c++ for too long.
cobbal
A: 

I don't believe *(2 + i) will work since the left hand operand needs to be your own type. You're actually telling the compiler to add your iterator to 2, which makes no sense. (i + 2) means move my iterator forward two indexes.

See the C++ Faq Lite for more information.

Stephen Newell
You can (and sometimes it makes sense), as pointed out by onebyone and Evan Teran.
Rasmus Kaj
Whether it makes sense or not, random access iterators are required to implement `2 + i`. It's in 24.1.5 of the standard. Sort of. It says both `a + n` and `n + a` must be valid. I can't immediately find where it says what the type is of `n`, but the specification of `std::advance` implies that it can be negative, hence must be a signed type.
Steve Jessop
The general solution here is to have something like operator+(int, your_iterator) defined outside the your_iterator class.
David Thornley
+3  A: 

First, you need an operator *(void) const.

[Edit: based on your existing operator, the following should do:

char &operator *(void) const {
    // bounds checking
    return *p;
}

]

Second, you need an operator+(int, exscape::string::iterator). A fairly common way to write this would be (in the iterator class):

friend const iterator operator+(const int offset, iterator out) {
    out += offset;
    return out;
}

Note that marking it friend makes it a non-member function, even though it is defined inside the class. You might also want to replace the operator+(int) with a non-member function, operator+(iterator,int), just so that you get the same implicit conversion rules applied to the LHS and RHS of the +.

[Another edit: as you point out in your comment, operator+ shouldn't be returning const iterator anyway - just return iterator. So for your code example, you don't actually need operator*()const. But you should have one anyway, because users might want to write code using const-modified instances of your class.]

Finally, standard containers with random access iterators (including std::string) define a signed difference_type as a member of the class. int might not be big enough to contain all possible offsets (for example on an LP64 architecture), but ptrdiff_t is a good candidate.

Steve Jessop
Thanks. I changed operator+ and operator- (the member ones) to non-const (I don't want to make operator* const, since it returns a reference that should be writable) and that solved the *(i + 2) case. As for the *(2 + i) case, I knew that a friend method was the solution and should have pointed that out to save you guys the trouble, sorry. The only trouble I had here was finding where in the file to put the code.BTW, since I'm new here: should I post in more detail what I did, to make it easier for others reading this in the future, and if so, how? (Edit my original post?)
It should be `const char }`. I think your question gave enough info for people to answer it. Too many people just say "it didn't work", you were right to post the relevant parts of the code and the compiler errors. Sometimes a complete, compilable program is required, but not on this occasion. And if you do want to give more information then yes, you should edit the question. Ideally also indicate that it's an edit, otherwise people who answered the question before the edit end up looking like they didn't read it...
Steve Jessop
+1 for mentioning bounds checking
luke
@luke: I'd love to claim credit, but the fact that those functions bounds check is just copied from the question. I don't blame other answers for omitting it.
Steve Jessop
UncleBens
Steve Jessop