views:

218

answers:

9

Before this is marked as duplicate, I'm aware of this question, but in my case we are talking about const containers.

I have 2 classes:

class Base { };
class Derived : public Base { };

And a function:

void register_objects(const std::set<Base*> &objects) {}

I would like to invoke this function as:

std::set<Derived*> objs;
register_objects(objs);

The compiler does not accept this. Why not? The set is not modifiable so there is no risk of non-Derived objects being inserted into it. How can I do this in the best way?

Edit:
I understand that now the compiler works in a way that set<Base*> and set<Derived*> are totally unrelated and therefor the function signature is not found. My question now however is: why does the compiler work like this? Would there be any objections to not see const set<Derived*> as derivative of const set<Base*>

A: 

Well, as stated in the question you mention, set<Base*> and set<Derived*> are different objects. Your register_objects() function takes a set<Base*> object. So the compiler do not know about any register_objects() that takes set<Derived*>. The constness of the parameter does not change anything. Solutions stated in the quoted question seem the best things you can do. Depends on what you need to do ...

neuro
+2  A: 

std::set<Base*> and std::set<Derived*> are basically two different objects. Though the Base and Derived classes are linked via inheritance, at compiler template instantiation level they are two different instantiation(of set).

aJ
Thanks @Daniel Daranas for correcting typo.
aJ
You're welcome. Good answer.
Daniel Daranas
+1  A: 

Firstly, It seems a bit odd that you aren't passing by reference ...

Secondly, as mentioned in the other post, you would be better off creating the passed-in set as a std::set< Base* > and then newing a Derived class in for each set member.

Your problem surely arises from the fact that the 2 types are completely different. std::set< Derived* > is in no way inherited from std::set< Base* > as far as the compiler is concerned. They are simply 2 different types of set ...

Goz
I corrected the reference passing, this was a typo in my question, in my code I did this correct.
Peter Smit
They aren't just 'simply different': there surely is a relation between the types, only it's not mere inheritance.
xtofl
but template substitution works by substituting the template parameter into the class doesn't it? The 2 definitions can be thought of as std::set_base_ptr and std::set_derived_ptr, ie fundamentally different. If it CAN be done I'd be very surprised but would be happy to concede that i've misunderstood how template substitution works (Which would, no doubt, help extend my template knowledge).
Goz
+2  A: 

If your register_objects function receives an argument, it can put/expect any Base subclass in there. That's what it's signature sais.

It's a violation of the Liskov substitution principle.

This particular problem is also referred to as Covariance. In this case, where your function argument is a constant container, it could be made to work. In case the argument container is mutable, it can't work.

xtofl
+2  A: 

Take a look here first: Is array of derived same as array of base. In your case set of derived is a totally different container from set of base and since there is no implicit conversion operator is available to convert between them , compiler is giving an error.

Naveen
A: 

As you are aware, the two classes are quite similar once you remove the non-const operations. However, in C++ inheritance is a property of types, whereas const is a mere qualifier on top of types. That means that you can't properly state that const X derives from const Y, even when X derives from Y.

Furthermore, if X does not inherit from Y, that applies to all cv-qualified variants of X and Y as well. This extends to std::set instantiations. Since std::set<Foo> does not inherit from std::set<bar>, std::set<Foo> const does not inherit from std::set<bar> const either.

MSalters
+12  A: 

The reason the compiler doesn't accept this is that the standard tells it not to.

The reason the standard tells it not to, is that the committee did not what to introduce a rule that const MyTemplate<Derived*> is a related type to const MyTemplate<Base*> even though the non-const types are not related. And they certainly didn't want a special rule for std::set, since in general the language does not make special cases for library classes.

The reason the standards committee didn't want to make those types related, is that MyTemplate might not have the semantics of a container. Consider:

template <typename T>
struct MyTemplate {
    T *ptr;
};

template<>
struct MyTemplate<Derived*> {
    int a;
    void foo();
};

template<>
struct MyTemplate<Base*> {
    std::set<double> b;
    void bar();
};

Then what does it even mean to pass a const MyTemplate<Derived*> as a const MyTemplate<Base*>? The two classes have no member functions in common, and aren't layout-compatible. You'd need a conversion operator between the two, or the compiler would have no idea what to do whether they're const or not. But the way templates are defined in the standard, the compiler has no idea what to do even without the template specializations.

std::set itself could provide a conversion operator, but that would just have to make a copy(*), which you can do yourself easily enough. If there were such a thing as a std::immutable_set, then I think it would be possible to implement that such that a std::immutable_set<Base*> could be constructed from a std::immutable_set<Derived*> just by pointing to the same pImpl. Even so, strange things would happen if you had non-virtual operators overloaded in the derived class - the base container would call the base version, so the conversion might de-order the set if it had a non-default comparator that did anything with the objects themselves instead of their addresses. So the conversion would come with heavy caveats. But anyway, there isn't an immutable_set, and const is not the same thing as immutable.

Also, suppose that Derived is related to Base by virtual or multiple inheritance. Then you can't just reinterpret the address of a Derived as the address of a Base: in most implementations the implicit conversion changes the address. It follows that you can't just batch-convert a structure containing Derived* as a structure containing Base* without copying the structure. But the C++ standard actually allows this to happen for any non-POD class, not just with multiple inheritance. And Derived is non-POD, since it has a base class. So in order to support this change to std::set, the fundamentals of inheritance and struct layout would have to be altered. It's a basic limitation of the C++ language that standard containers cannot be re-interpreted in the way you want, and I'm not aware of any tricks that could make them so without reducing efficiency or portability or both. It's frustrating, but this stuff is difficult.

Since your code is passing a set by value anyway, you could just make that copy:

std::set<Derived*> objs;
register_objects(std::set<Base*>(objs.begin(), objs.end());

[Edit: you've changed your code sample not to pass by value. My code still works, and afaik is the best you can do other than refactoring the calling code to use a std::set<Base*> in the first place.]

Writing a wrapper for std::set<Base*> that ensures all elements are Derived*, the way Java generics work, is easier than arranging for the conversion you want to be efficient. So you could do something like:

template<typename T, typename U>
struct MySetWrapper {
    // Requirement: std::less is consistent. The default probably is, 
    // but for all we know there are specializations which aren't. 
    // User beware.
    std::set<T> content;
    void insert(U value) { content.insert(value); }
    // might need a lot more methods, and for the above to return the right
    // type, depending how else objs is used.
};

MySetWrapper<Base*,Derived*> objs;
// insert lots of values
register_objects(objs.content);

(*) Actually, I guess it could copy-on-write, which in the case of a const parameter used in the typical way would mean it never needs to do the copy. But copy-on-write is a bit discredited within STL implementations, and even if it wasn't I doubt the committee would want to mandate such a heavyweight implementation detail.

Steve Jessop
A: 

You are quite right that this is logically allowable, but it would require further language features. They are available in C# 4.0, if you're interested in seeing another language's way of doing it. See here: http://community.bartdesmet.net/blogs/bart/archive/2009/04/13/c-4-0-feature-focus-part-4-generic-co-and-contra-variance-for-delegate-and-interface-types.aspx

Daniel Earwicker
A: 

Didn't see it linked yet, so here's a bullet point in the C++ FAQ Lite related to this:

http://www.parashift.com/c++-faq-lite/proper-inheritance.html#faq-21.3

I think their Bag-of-Apples != Bag-of-Fruit analogy suits the question.

RAC