views:

414

answers:

3

I'm learning C++ on my own, and I thought a good way to get my hands dirty would be to convert some Java projects into C++, see where I fall down. So I'm working on a polymorphic list implementation. It works fine, except for one strange thing.

The way I print a list is to have the EmptyList class return "null" (a string, not a pointer), and NonEmptyList returns a string that's their data concatenated with the result of calling tostring() on everything else in the list.

I put tostring() in a protected section (it seemed appropriate), and the compiler complains about this line (s is a stringstream I'm using to accumulate the string):

s << tail->tostring();

Here's the error from the compiler:

../list.h: In member function 'std::string NonEmptyList::tostring() [with T = int]':
../list.h:95:   instantiated from here
../list.h:41: error: 'std::string List::tostring() [with T = int]' is protected
../list.h:62: error: within this context

Here's most of list.h:

template <class T> class List;
template <class T> class EmptyList;
template <class T> class NonEmptyList;

template <typename T>
class List {
public:
    friend std::ostream& operator<< (std::ostream& o, List<T>* l){
        o << l->tostring();
        return o;
    }
    /* If I don't declare NonEmptyList<T> as a friend, the compiler complains
     * that "tostring" is protected when NonEmptyClass tries to call it
     * recursively.
     */
    //friend class NonEmptyList<T>;

    virtual NonEmptyList<T>* insert(T) =0;
    virtual List<T>* remove(T) =0;
    virtual int size() = 0;
    virtual bool contains(T) = 0;
    virtual T max() = 0;

    virtual ~List<T>() {}
protected:
    virtual std::string tostring() =0;
};

template <typename T>
class NonEmptyList: public List<T>{
    friend class EmptyString;
    T data;
    List<T>* tail;
public:
    NonEmptyList<T>(T elem);
    NonEmptyList<T>* insert(T elem);
    List<T>* remove(T elem);
    int size() { return 1 + tail->size(); }
    bool contains(T);
    T max();
protected:
    std::string tostring(){
        std::stringstream s;
        s << data << ",";

        /* This fails if List doesn't declare NonEmptyLst a friend */
        s << tail->tostring();

        return s.str();
    }
};

So declaring NonEmptyList a friend of List makes the problem go away, but it seems really strange to have to declare a derived class as a friend of a base class.

+7  A: 

Because tail is a List<T>, the compiler is telling you that you can't access a protected member of another class. Just as in other C-like languages, you can only access protected members in your instance of the base class, not other people's.

Deriving class A from class B does not give class A access to every protected member of class B on all instances that are of type class B or derived from that type.

This MSDN article on the C++ protected keyword may be helpful in clarifying.

Update

As suggested by Magnus in his answer, in this instance, a simple fix might be to replace the call to tail->tostring() with the << operator, which you've implemented for List<T> to provide the same behavior as tostring(). That way, you wouldn't need the friend declaration.

Jeff Yates
Ah, ok, I think I understand. So I can access protected members of a base class within a class definition, but not "from outside," and since `tail` is an instance, doing `tail->tostring()` is coming "from outside." Right? (forgive my ignorance)
slide_rule
Pretty much. This MSDN article might help. http://msdn.microsoft.com/en-us/library/e761de5s.aspx
Jeff Yates
I stumbled across this hole today. You can do this: struct voyeur : List<T> { using List<T>::tostring; }; (tail->*(
Johannes Schaub - litb
A: 

From your code, You declare List* tail as a member of the class NonEmptyList, that's why you cannot access it. If you want to access a protected method from base class, then you need to call base->tostring();

You are doing composition, NOT inheritance here.

And I am not sure why you end up this class design by converting a java project, if you inherit from a base class, your normally don't want to declare another instance of base class as your member variable. This approach doesn't look very smooth to me. I don't think you even need do that in Java.

J.W.
It would make more sense (I think) if I included more code that I didn't think was germane: tail needs to be of the base class because it could be a NonEmptyList or an EmptyList. It might be doing things in an overly-complex way, but then if I wanted simplicity, I'd use STL. But it's a good point, thanks.
slide_rule
@J.W.: Huh? It looks like a recursive data structure combined with the Null Object pattern.
bk1e
bk1e, yes, good point, I guess I didn't go through the code in detail enough.Thanks for pointing out.
J.W.
+4  A: 

As Jeff said, the toString() method is protected and can't be called from your NonEmptyList class. But you have already provided a std::ostream& operator<< for the List class, so why dont you use it in NonEmptyList?

template <typename T>
class NonEmptyList: public List<T>{
// ..
protected:
    std::string tostring(){
        std::stringstream s;
        s << data << ",";

        s << tail; // <--- Here :)

        return s.str();
    }
};
Magnus Skog
Brilliant - that works beautifully. Thanks for the tip; I'm still not sure I'm understanding the access control issue.
slide_rule
Good suggestion.
Jeff Yates