2
\$\begingroup\$

Idea behind the class is same as boost::string_ref or llvm::SrtingRef.

StringRef is immutable and not-owning.

I did not implemented substring() yet, because I will not need it.

Example usage:

std::string name = "xxxx";

void store(const StringRef &s);

void something(const StringRef &s){
   //... do something here, for example
   if (s == "hi"){
      std::string a = s + name;
      store(a);
   }
}

int main(){
   // function can be call like this:
   something("Hello");
   // or with std::string:
   std::string a = "Hello";
   something(a);
}

stringref.h

#ifndef MY_STRING_REF_H
#define MY_STRING_REF_H

#include <string>
#include <ostream>

class StringRef{
public:
    StringRef() = default;

    StringRef(const char *data, size_t const size);
    StringRef(const char *data);
    StringRef(const std::string &s);

    // ==================================

    const char *data() const{
        return _data;
    }

    size_t size() const{
        return _size;
    }

    // ==================================

    bool empty() const;

    char charAt(size_t index) const;

    std::string toString() const;

    // ==================================

    int compare(const char *data) const;
    int compare(const std::string &s) const;
    int compare(const StringRef &sr) const;

    // ==================================

    operator std::string() const;

    char operator [] (size_t index) const;

    bool operator ==(const char *data) const;
    bool operator ==(const std::string &data) const;
    bool operator ==(const StringRef &data) const;
    bool operator ==(char c) const;

    bool operator !=(const char *data) const;
    bool operator !=(const std::string &data) const;
    bool operator !=(const StringRef &data) const;
    bool operator !=(char c) const;

    // ==================================

    const char *c_str() const{
        return data();
    }

    size_t length() const{
        return size();
    }

private:
    size_t          _size   = 0;
    const char      *_data  = __zeroStr;

private:
    constexpr
    static const char   *__zeroStr = "";
};

std::ostream& operator << (std::ostream& os, const StringRef &sr);

// ==================================

inline bool StringRef::empty() const{
    return _size == 0;
}

inline char StringRef::charAt(size_t const index) const{
    return _data[index];
}

inline std::string StringRef::toString() const{
    return std::string(_data, _size);
}

// ==================================

inline int StringRef::compare(const std::string &s) const{
    return compare(s.data() );
}

inline int StringRef::compare(const StringRef &sr) const{
    return compare(sr.data() );
}

// ==================================

inline StringRef::operator std::string() const{
    return toString();
}

inline char StringRef::operator [] (size_t const index) const{
    return charAt(index);
}

// ==================================

inline bool StringRef::operator ==(const char *data) const{
    return compare(data) == 0;
}

inline bool StringRef::operator ==(const std::string &data) const{
    return compare(data) == 0;
}

inline bool StringRef::operator ==(const StringRef &data) const{
    return compare(data) == 0;
}

inline bool StringRef::operator ==(char const c) const{
    return _size == 1 && _data[0] == c;
}

// ==================================

inline bool StringRef::operator !=(const char *data) const{
    return ! (*this == data);
}

inline bool StringRef::operator !=(const std::string &data) const{
    return ! (*this == data);
}

inline bool StringRef::operator !=(const StringRef &data) const{
    return ! (*this == data);
}

inline bool StringRef::operator !=(char const c) const{
    return ! (*this == c);
}

#endif

stringref.cc

#include "stringref.h"

#include <cstring>

// ==================================

std::ostream& operator << (std::ostream& os, const StringRef &sr) {
    for(size_t i = 0; i < sr.size(); ++i)
        os << sr[i];
    return os;
}

// ==================================

StringRef::StringRef(const char *data, size_t const size) :
        _size(size),
        _data(size ? data : __zeroStr){}

StringRef::StringRef(const char *data) :
        StringRef(data, data ? strlen(data) : 0){}

StringRef::StringRef(const std::string &s) :
        _size(s.size()),
        _data(s.data()){}

int StringRef::compare(const char *data) const{
    // Lazy based on LLVM::StringRef
    // http://llvm.org/docs/doxygen/html/StringRef_8h_source.html

    auto data_size = strlen(data);

    // check prefix
    if ( int res = memcmp(_data, data, std::min(_size, data_size ) ) )
        return res < 0 ? -1 : +1;

    // prefixes match, so we only need to check the lengths.
    if (_size == data_size)
        return 0;

    return _size < data_size ? -1 : +1;
}
\$\endgroup\$

1 Answer 1

6
\$\begingroup\$

Clean up the Interface

Your StringRef has a lot of member functions. It seems to be taking a cue from std::string, which has even more. A lot of those functions aren't really necessary. charAt(), operator std::string(), toString(), compare(). Do you really need any of those? I arranged those in decreasing order of "definitely remove this." For sure, charAt() should not exist. If you want to keep the string conversion, I would call it explicit operator std::string().

Indexing

You have:

char operator [] (size_t index) const;

I would propose instead to have:

char const& operator [] (size_t index) const;

The reason being that &s[0] is a fairly commonly used idiom to get a pointer to the first element... which would fail to compile now. I don't think that's something we should prematurely disallow, so let's make it work. Obviously make it char const& (and not char&) since you want to keep it immutable.

__zeroStr

First of all, this is a reserved name (as is any name that contains a double underscore). Secondly, it does nothing for you. Just have:

class StringRef {
    size_t _size = 0;
    const char* _data = "";
};

Repetition

You have four operator!=s defined. All of them forward to the appropriate operator==, which is good. But we still have to rewrite everything. We could make this a non-member template:

template <typename T>
auto operator!=(StringRef const& lhs, T const& rhs)
-> decltype(lhs == rhs)
{
    return !(lhs == rhs);
}

Now we just have the one.

bool operator==(char) const;

Why?

compare and strlen()

Your "primary" compare starts with:

int StringRef::compare(const char *data) const {
    auto data_size = strlen(data);
    ...
}

But two of our cases for compare (std::string and StringRef) know their own sizes. Why would we drop this information? Instead, use the case that doesn't know to simply forward to one that does:

int compare(const char* data) const { return compare(data, strlen(data)); }
int compare(const std::string& data) const { return compare(data.c_str(), data.size()); }
int compare(const StringRef& data) const { return compare(data.c_str(), data.size()); }

Which brings me to the main compare. We have two null-terminated strings. Both are null-terminated. So what happens when the first one is shorter than the other? Well, if it's a strict prefix, we'll eventually get to the \0 and that char will compare smaller. That's all we need to know:

int compare(const char* data, size_t len) {
    return memcmp(_data, data, std::max(len, _size));
}

That's quite a bit shorter.

operator<<

You are outputting each character one at a time. That's fine, but you could instead use the write() function:

std::ostream& operator << (std::ostream& os, const StringRef &sr) {
    return os.write(sr.c_str(), sr.size());
}
\$\endgroup\$
6
  • \$\begingroup\$ Thank you for great answer. Some of the things are really nice. I will need to check them one by one. int compare(const char* data, size_t len) is made like this, because I need int output similar to strcmp(), rather just equality. in fact whole compare is stolen from llvm. I did not understand the comment for compare(char) - what is wrong there - note I do not have c-tor from char, so there is no way to do the comparisons. \$\endgroup\$
    – Nick
    Sep 14, 2015 at 20:52
  • \$\begingroup\$ memcmp(_data, data, std::max(len, _size)); will definitely give wrong result on compare "aaa" and "aaab". How can I do sr == 'a' if I do not have operator==(char) ? 'a' probably will be casted as std::string? \$\endgroup\$
    – Nick
    Sep 14, 2015 at 21:08
  • \$\begingroup\$ is operator!= same as so called perfect forwarding ? \$\endgroup\$
    – Nick
    Sep 14, 2015 at 21:24
  • \$\begingroup\$ last question auto operator!=() - why you use decltype? isn't return type always bool ? \$\endgroup\$
    – Nick
    Sep 14, 2015 at 21:41
  • \$\begingroup\$ compare using memcpy might give wrong result if tou are using max. suppose you have aaa is the first 3 characters of aaab. then your version will return 0. another issue will be, if aaa is dinamically allocated. then accessing memory after last character will give memory violation. this is why llvm did it that way (and i copy it from there) \$\endgroup\$
    – Nick
    Sep 15, 2015 at 5:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.