views:

259

answers:

3

I want to create a substr method in C++ in a string class that I made.

The string class is based on C-style string of course, and I take care of the memory management.

I want to write a substr(start, length) function that can work on the regular way:

CustomString mystring = "Hello";

cout << mystring.substr(0,2); // will print "He"

And also in this way:

mystring.substr(1,3) = "DD"; // mystring will be "HDDo"

Notice that even though I get a 3 chars long sub-string, I put in the assignment only 2 chars and the output string will be HDDo, still.

Any idea how to get this done?

Thanks!

+6  A: 

To support that, you'll probably have to write your substr() to return a proxy object that keeps track of what part of the original string is being referred to. The proxy object will overload operator=, and in it will replace the referred-to substring with the newly assigned one.

Edit in response to comments: the idea of a proxy is that it's similar enough to the class for which it's a proxy that returning a proxy is still a closed operation -- i.e. from the user's viewpoint, all that's visible is the original type of object, but it has capabilities that wouldn't be possible (or would be much more difficult to implement) without the proxy. In this case, we the proxy class would be private to the string class, so the user could never create an instance of the proxy class except as a temporary. That temporary can be used to modify its parent string if you assign to it. Using the proxy in any other way just yields a string.

As to what this buys you over attempting to do it all inside the original string: each proxy object is a temporary object -- the compiler can/will/does keep track of how to create temporaries as needed, destroys them properly at the end of a full expression, etc. The compiler also keeps track of what substring a particular assignment refers to, automatically converts one to a string when we try to use its value, and so on. Simply put, the compiler handles nearly all the hard work involved.

Here's some working code. The surrounding string class is pretty minimal (e.g. it has no searching capability). I'd expect to add a fair amount to a useful version of the string class. The proxy class, however, is complete -- I wouldn't expect to see it change much (if at all) in a feature-complete version of the string class.

#include <vector>
#include <algorithm>
#include <iostream>
#include <iterator>

class string { 
    std::vector<char> data;
public:
    string(char const *init) { 
        data.clear();
        data.assign(init, init+strlen(init));
    }

    string(string const &s, size_t pos, size_t len) {
        data.assign(s.data.begin()+pos, s.data.begin()+pos+len);
    }

    friend class proxy;

    class proxy {
        string &parent;
        size_t pos;
        size_t length;
    public:
        proxy(string &s, size_t start, size_t len) : parent(s), pos(start), length(len) {}

        operator string() { return string(parent, pos, length); }

        proxy &operator=(string const &val) { 
            parent.data.erase(parent.data.begin()+pos, parent.data.begin()+pos+length);
            parent.data.insert(parent.data.begin()+pos, val.data.begin(), val.data.end());
            return *this;
        }
    };

    proxy substr(size_t start, size_t len) { 
        return proxy(*this, start, len);
    }

    friend std::ostream &operator<<(std::ostream &os, string const &s) { 
        std::copy(s.data.begin(), s.data.end(), std::ostream_iterator<char>(os));
        return os;
    }
};

#ifdef TEST

int main() { 
    string x("Hello");

    std::cout << x << std::endl;

    std::cout << x.substr(2, 3) << std::endl;

    x.substr(2, 3) = "DD";

    std::cout << x << std::endl;

    return 0;
}

#endif

Edit 2: As far as substrings of substrings go, it depends. The one situation that's not currently covered is if you want to assign to a substring of a substring, and have it affect the original string. If you want something like x=y.substr(1,4).substr(1,2); it'll work fine as-is. The first proxy will be converted to a string, and the second substr will be invoked on that string.

If you want: x.substr(1,4).substr(1,2) = "whatever"; it won't currently work. I'm not sure it accomplishes much, but on the assumption that it does, the addition to support it is fairly minimal -- you'd add a substr member to proxy:

proxy substr(size_t start, size_t len) { 
    return proxy(parent, pos+start, len);
}
Jerry Coffin
But what if I want a substring of a substring?
Daniel Earwicker
@Earwicker: Then `proxy::operator string()` should kick in.
sbi
Daniel Earwicker
@Earwicker: Yes, you're right. To do what the OP wants one either needs polymorphism (dynamic or static) or to handle proxying within the string class as you suggested. The latter still seems hackish and ugly to me, though.
sbi
A: 

Presumably you want substr to return a string, rather than some other proxy class. You'd therefore need to make your string class capable of holding a pointer to its own copy of the string data and also a pointer to another string object that it was created from (as the return value of substr), along with information about which part of the string it was created from.

This might get quite complicated when you call substr on a string returned from another call to substr.

The complexity is probably not worth the attractiveness of the interface.

Daniel Earwicker
If one string references another one, then what is it if not a proxy object? And given the complexity you describe, what would that buy you over Jerry's proxy idea?
sbi
The point is that the taking of a substring should be a closed operation - it should return another object of the same type, not *another* proxy class. i.e. string itself would serve as the proxy. Also, the same complexity would obviously exist in *any* fully capable proxy class.
Daniel Earwicker
This will give problems for this case: CustomString mystring = "Hello"; CustomString mysub = mystring.substr(1,3); mysub="DD"; Presumably you'd want the assignment to mysub to break the connection to the parent.
Mark Ransom
A: 

The first requirement is simple; look up operator's standard implementation.

Loosely, c_string& substr(int, int)

The second part, not so much, I don't think. It'll look similar, I believe. However, I'll think about it and get back to you over the weekend.

Paul Nathan