tags:

views:

301

answers:

4

I want to extend the C++ string class, returning subclass references (instead of parent string reference), but this code excerpt...

#include <string>
using namespace std;

class mystring : public string
{
public:
 mystring&  left( int cnt )
 {   return  (mystring&)mystring( substr(0,cnt) );
 }
};

produces this VS8 compiler error:

error C2440: '' : cannot convert from 'std::basic_string<_Elem,_Traits,_Ax>' to 'mystring'

What's the right way to declare mystring::left() so the compiler will stop complaining, hopefully also eliminating the cast?

+4  A: 

The problem seems to be that substr returns a std::string, not an instance of your subclass. Do you have a mystring::mystring(const std::string&) constructor?

And why on earth are you subclassing std::string anyway? :D

It's probably better to write a free function instead of subclassing.

#include <string>

namespace mystring {
  std::string left(const std::string& str, int cnt)
  {   
     return str.substr(0,cnt);
  }
}
Nicolás
+1, but remove "probably" ;-) C++ programmers coming from other languages just have to accept that not everything in C++ should be a member function. This is surprisingly difficult to assimilate, even once you see how useful the free functions in <algorithm> are. I found that this article by Herb Sutter helped: http://www.gotw.ca/gotw/084.htm. But in my case, I had been softened up by Python before I reached C++. Non-member `len` will probably always freak me out just a little bit...
Steve Jessop
+5  A: 

error C2440: '' : cannot convert from 'std::basic_string<_Elem,_Traits,_Ax>' to 'mystring'

From MSDN page on C2440:

C2440 can also occur for an incorrect use of a user-defined conversion. For more information on user-defined conversions, see User-Defined Conversions). The following sample generates C2440.

The error you hit is because you are trying to return a base class object via a reference to an object of the derived class. It works the other way well. Hence, your need for the cast -- which is bad.

Standard containers (including std::string) are not suitable for inheritance, use aggregation instead.

You don't need the cast to string&.

Try something like this:

#include <string>
using namespace std;

class mystring     {
public:
 // create an appropriate conversion op/ctor between string -> mystring
 mystring(string const& s) : impl(s) {}
 mystring  left( int cnt )
 {   
     return mystring( impl.substr(0,cnt) );
 }
 string impl;

};
dirkgently
Taken the example directly still gives *C2440* - mystring is not constructible from `std::string` as it has no constructor that takes std::string or a type it can be converted to.
Georg Fritzsche
Perhaps you should note *why* `std::string` shouldn't be subclassed. E.g., destructors and all of that lovely stuff.
greyfade
Sorry dirkgently, but i don't get the continued upvotes: in context of the question it is wrong - the OP is trying to construct an instance of `mystring` from `std::string`, which doesn't work as `mystring` isn't constructible from `std::string`.
Georg Fritzsche
@gf: Ah, I see the typo -- there was a problem in the `left()` implementation. However, I pointed out a more basic flaw in the design. As for returning a `mystring` -- it is simply a matter creating an appropriate conversion operator (which I left as an exercise and I should have mentioned). As for the upvotes, I will make no comment but do fix your example.
dirkgently
Still, you're referring to the C2440, which is given for another reason then you're saying.
Georg Fritzsche
@gf: Updated my post w/ MSDN reference. HTH.
dirkgently
+3  A: 

be careful when deriving from a class in the std namespace.

see permalink to answer for my own question from peterchen.

EDIT: the essence of the article referenced by the permalink is: you can derive from std::string. But you should not introduce a new member variable.

+1  A: 

The compiler already tells you what the problem is - it can't convert from std::string to mystring as your class isn't constructible from std::string.

You shouldn't subclass the standard libraries types however, they aren't suited for that - use composition.

If we leave that issue aside for a moment:
You need to supply a constructor that takes a std::string. But that still leaves a problem: you're returning a reference to a temporary - just return by value here.

Your class could now look like this:

class mystring  
{
    std::string m_string;
public:
    mystring(const std::string& s) : m_string(s) {}

    mystring left(std::string::size_type cnt) {
        return m_string.substr(0,cnt);
    }
};
Georg Fritzsche