Search code examples
c++inheritancestdstring

Can not create a wrapper around std::string that results in only "syntax sugar"


I know that std::string is not designed for inheritance, however, I wonder why this class definition doesn't compile:

using std::string;
class ExtendedString: public string
{
public:
    using string::string;
    ExtendedString left(size_type n) const {
        return substr(0, n>size()?size():n);
    }
};

I get this error:

../../src/capel-tool.cpp:21:17: error: could not convert ‘std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::substr(std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::size_type, std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::size_type) const [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>; std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::size_type = long unsigned int](0, ((n > ((const ExtendedString*)this)->ExtendedString::<anonymous>.std::__cxx11::basic_string<char>::size()) ? ((const ExtendedString*)this)->ExtendedString::<anonymous>.std::__cxx11::basic_string<char>::size() : n))’ from ‘std::__cxx11::basic_string<char>’ to ‘ExtendedString’

I expected that the left function would use any default constructor from std::string.

Even if I add an explicit constructor like this:

using std::string;
class ExtendedString: public string
{
public:
    using string::string;
    ExtendedString left(size_type n) const {
        return substr(0, n>size()?size():n);
    }

    explicit ExtendedString(const std::string &s);
};

I still get the same error.

Only when I add a normal constructor:

using std::string;
class ExtendedString: public string
{
public:
    using string::string;
    ExtendedString(const std::string &s);
    ExtendedString left(size_type n) const {
        return substr(0, n>size()?size():n);
    }
};

The code compiles ok.

Now imagine I want to make the same with a class that is designed for inheritance. Can not I just create this kind of wrapper in a way that the classes can be used interchangeablily without the need to create constructors thad would be created and called instead of being only "syntax sugar"

EDIT 1:

This suggested question does explain why my code does not work, but it does not propose any alternatives, as the accepted question does.


Solution

  • The problem with your code is that substr returns a std::string, not an ExtendedString. No implicit conversion exists between the types, so that return statement cannot compile. There is an implicit conversion up the inheritance hierarchy, i.e. you can convert ExtendedString to std::string, but not the other way around.

    To solve this for just your left function:

    • you could make the constructor implicit: ExtendedString(const std::string&)
    • construct explicitly: return ExtendedString(substr(...))

    However, in general, what you're trying to do is just not nicely possible in C++. Every member function that you inherit from std::string is not going to return ExtendedString, so you have the wrong interface. C++ gives you no way of defining extension methods, which is the feature you're imitating.

    "Clean" Alternatives

    • create an implicit conversion from std::string to ExtendedString, but this results in unnecessary copying
      • note: this is what you've done when declaring ExtendedString(const std::string &s);
    • inherit privately from std::string or better yet, wrap it as a member
      • create wrapper functions for every member function that std::string has, and make them take/return ExtendedString where necessary
    • just create free functions (see below)
    std::string left(const std::string& s, std::size_t n) {
        return s.substr(0, std::min(s.size(), n));
    }
    

    As annoying as it is to sometimes call str.function() and sometimes function(str), that inconsistency is a small price compared to any alternative.