views:

387

answers:

4

C++ being a value oriented language doesn't seem to support OO (and thus sub-typing polymorphism) very well. As for parametric polymorphism, lack of type inference on type parameters and verbose syntax of templates makes them challenging to use.

Please note that the only languages I know moderately well are Java (sub-typing polymorphism) and Haskell (parametric polymorphism). Both languages are leaned towards one kind of polymorphism. However C++ supports both (to some extent), but both seem to work in a matter that I find unintuitive. So when programming in C++ I have a pretty hard time in deciding what way I should exactly code.

So my question is what kind of polymorphism is considered more idiomatic in C++?

EDIT 1:

Explanation of my "C++ doesn't support OO well" point:

Dynamic method dispatch and LSP are very common in OO, aren't they? But when it comes to C++, applying these techniques without resorting to pointers (raw or smart) is not possible (or practical).
For example,consider a class Person with virtual method print which prints his name to the console. Let there be another class Student that extends Person and overrides print to print his name plus his school's name.

Now consider the following function:

void blah(const Person & p) {
  p.print();
}

Here if I pass a Student object, print method would invoke print from Person, not from Student. Thus it defies the very basic idea of subtyping polymorphism.

Now I am aware that I can use dynamic allocation (i.e. pointers) to achieve subtyping polymorphism in this case. However static allocation is more common in C++. Pointers are used as last resort (I remember having read it in some other thread here).So I find it difficult it difficult to reconcile the Good Practices that recommend static allocation over dynamic allocation (this is what I meant when I said C++ is value oriented) with subtyping polymorphism.

When using Java, I tend to use dynamic allocation all over and thus subtyping polymorphism is quite natural there. This is not the case with C++ however,

Hope my point is clear now.

EDIT 2:

Okay, the example I gave in my edit 1 is wrong. But my point is still valid and I have faced the problem many times. I am unable to recall all those cases top of my head.

Here's one case that comes to my mind.

In Java you can have reference of super type in your classes and then make them point to instances of any of its subtypes.

For example,

class A {
  B y1;
  B y2;
}

abstract class B {
  // yada yada
}

class B1 exyends B {
  // yada yada
}

class B2 extends B {
  // yada yada
}

Here the references y1 and y2 in A can be made to point to instances of either B1, B2 or any other subclass of B. C++ references cannot be reassigned. So I will have to use pointers here. So this provs that in C++ it's not possible to achieve all sorts of subtyping polymorphism without using pointers.

+2  A: 

Templates are evaluated at compile-time by creating basically a copy of the templated function or object. If you need polymorphism during runtime (ie: a std::vector<Base*> vec and vec->push_back(new Derived()) every now and then...) you're forced to use subtypes and virtual methods.

EDIT: I guess I should put forward the case where Templates are better. Templates are "open", in that a templated function or object will work with classes that you haven't made yet... so long as those classes fit your interface. For example, auto_ptr<> works with any class I can make, even though the standard library designers haven't thought of my classes really. Similarly, the templated algorithms such as reverse work on any class that supports dereferencing, and operator++.

When using subtyping, you have to write down the class hierarchy somewhere. That is, you need to say B extends A somewhere in your code... before you can use B like A. On the other hand, you DON'T have to say B implements Randomaccessiterator for it to work with templated code.

In the few situations where both satisfy your requirements, then use the one you're more comfortable with using. In my experience, this situation doesn't happen very often.

Dragontamer5788
You have made some good points I see. Thank you for your answer.
Jim
+3  A: 

Both options have their advantages. Java-style inheritance is far more common in real world C++ code. Since your code typically has to play well with others, I would focus on subtyping polymorphism first since that's what most people know well.

Also, you should consider whether polymorphism is really the right way to express the solution to your problems. Far too often people build elaborate inheritance trees when they aren't necessary.

Kristo
Please note that polymorphism is not *equal* to inheritance. There are other ways to achieve polymorphism and my question is exactly about that - what kind of polymorphism is more idiomatic in C++. So basically my question is *about the polymorphism.* I am not asking whether I should or should not use polymorphism for some problem.
Jim
And I'd disagree with your first statement. STL is mostly based on parametric polymorphism. There are very few places in C++ standard library that make use of subtyping polymorphism. Qt on the other hand uses mostly subtyping polymorphism (and that's the reason why some C++ gurus call it Smalltalkish and loath it.)
Jim
@Jim, your example code is wrong. Subtyping polymorphism does indeed work on references.
Kristo
@Jim, neither choice is more idiomatic C++. The language supports both by design. By "real world C++ code" I mean the code written by normal people, not the geniuses who wrote the standard library and Boost. Normal people use inheritance when they want polymorphism, so that's where you should focus your efforts to start.
Kristo
@Kristo: Yap. Acknowledged that in comments below Jerry's answer.
Jim
Thank you for the answer by the way.
Jim
+1  A: 

'Suck' is a pretty strong term. Perhaps we need to think about what Stroustrup was aiming for with C++

C++ was designed with certain pricniples in mind. Amongst others: It had to be backwards compatible with C. It shouldn't restrict the programmer from doing what they wanted to. You shouldn't pay for things you don't use.

So, that first one makes a pretty stiff standard to stick to - everything that was legal in C had to work (and work with the same effect) when compiled in C++. Because of this, a lot of necessary compromises were included. On the other hand, C++ also gives you a lot of power (or, as it has been described for C, 'enough rope to hang yourself.') With power comes responsibility, and the compiler won't argue with you if you choose to do something stupid.

I'll admit now, it's been about 15 years since I last looked at Haskell, so I'm a bit rusty on that - but parametric polymorphism (full type safety) can always be overridden in C++. Subtyping polymorphism can to. Esentially, anything can be overridden - the compiler won't argue with you if you insist on casting one pointer type to another (no matter how insane.)

So, having got that out of the way, C++ does give lots of options with polymorphism. The classic public inheritance models 'is-a' - sub-classing. It's very common.

Protected inheritance models 'is-implemented-in-terms-of' (inheriting implementation, but not interface)

Private inheritance models 'is-implemented-using' (containing the implementation)

The latter two are much less common. Aggregation (creating a class instance inside the class) is much more common, and often more flexible.

But C++ also supports multiple inheritance (true implementation and interface multiple inheritance), with all the inherent complexity and risks of repeated inheritance that brings (the dreaded diamond pattern) - and also ways of dealing with that. (Scott Myers 'Effective C++' and 'More Effective C++' will help untangle the compexities if you're interested.)

I'm not convinced that C++ is a 'value oriented language' to the exclusion of other things. C++ can be what you want it to be, pretty much. You just need to know what it is you want, and how to make it do it. It's not that C++ sucks, so much as C++ is very sharp, and you can easily cut yourself.

Ragster
Your post doesn't answer my question.
Jim
Oh. I thought the line 'The classic public inheritance models 'is-a' - sub-classing. It's very common.' would have been the answer to the specific question (historically subtyping would be idiomatic). Although it's insufficient in itself as the subject is much broader, hence all the other stuff. Just because it's idiomatic, doesn't mean it's limited, and flexibility is bothe blessing and a curse.
Ragster
+10  A: 

Having added the fifth vote to reopen gives me a chance at being the first to add another reply. Let's start with the claim that C++ doesn't support OO well. The example given is:

Now consider the following function:

void blah(const Person & p) {
  p.print();
}

Here if I pass a Student object, print method would invoke print from Person, not from Student. Thus it defies the very basic idea of subtyping polymorphism.

To make a long story short, this example is just plain wrong -- or more accurately, the claim made about the example is wrong. If you pass a Student object to this function, what will be invoked will be Student::print, not Person::print as claimed above. Thus, C++ implements polymorphism exactly as the OP apparently wishes.

The only part of this that isn't idiomatic C++ is that you normally use operator<< to print out objects, so instead of print (apparently) printing only to std::cout, you should probably have it take a parameter, and instead of blah, overload operator<<, something like:

std::ostream &operator<<(std::ostream &os, Person const &p) { 
    return p.print(os);
}

Now, it is possible to create a blah that would act as described, but to do so you'd have to have it take its parameter by value:

void blah(Person p) { 
    p.print();
}

So there is some degree of truth to the original claim -- specifically, when/if you want to use polymorphism, you do need to use pointers or references.

Note, however, that this isn't related (any more than peripherally) to how you allocate objects. You can pass by reference regardless of how the object in question was allocated. If a function takes a pointer, you can pass the address of an automatically or statically allocated object. If it takes a reference, you can pass a dynamically allocated object.

As far as type inference goes, C++ has it for function templates, but not class templates. C++0x adds decltype and a new meaning for auto (which has been a reserved word, but essentially never used almost since the dawn of C) that allow type inference for a wider variety of situations. It also adds lambdas (the lack of which really is a serious problem with the current C++), which can use auto. There are still situations where type inference isn't supported, but would be nice -- but at least IMO, auto (in particular) reduces that quite a bit.

As far as verbosity goes, there's little question that it's at least partly true. Somewhat like Java, your degree of comfort in writing C++ tends to depend to at least some degree on an editor that includes various "tricks" (e.g., code completion) to help reduce the amount you type. Haskell excels in this respect -- Haskell lets you accomplish more per character typed than almost any other language around (APL being one of the few obvious exceptions). At the same time, it's worth noting that "generics" (in either Java or C#) are about as verbose, but much less versatile than C++ templates. In terms of verbosity, C++ stands somewhere between Haskell at (or close to) one extreme, and Java and C# at (or, again, close to) the opposite extreme.

Getting to the original question of which is used more often: there was a time when C++ didn't have templates, so essentially your only choice was subtyping. As you can probably guess, at that time it was used a lot, even when it wasn't really the best choice.

C++ has had templates for a long time now. Templates are now so common that they're essentially unavoidable. Just for example, IOStreams, which originally used only inheritance, now also use templates. The standard containers, iterators, and algorithms all use templates heavily (and eschew inheritance completely).

As such, older code (and new code from coders who are older or more conservative) tends to concentrate primarily or exclusively on subtyping. Newer and/or more liberally written code, tends to use templates more. At least in my experience, most reasonably recent code uses a mixture of both. Between the two, I'll normally use subtyping when I have to, but prefer templates when they can do the job.

Edit: demo code showing polymorphism:

#include <iostream>

class Person { 
public:
    virtual void print() const { std::cout << "Person::print()\n"; }
};

class Student : public Person { 
public:
    virtual void print() const { std::cout << "Student::print()\n"; }
};

void blah(const Person &p) { 
    p.print();
}

int main() { 
    Student s;
    blah(s);
    return 0;
}

result (cut and pasted from running code above on my computer, compiled with MS VC++):

Student::print()

So yes, it does polymorphism exactly as you'd want -- and note that in this example, the object in question is allocated on the stack, not using new.

Edit 2: (in response to edit of question):

It's true that you can't assign to a reference. That's orthogonal to questions of polymorphism though -- it doesn't matter (for example) whether what you want to assign is of the same or different type from what it was initialized with, you can't do an assignment either way.

At least to me, it would seem obvious that there must be some difference in capabilities between references and pointers, or there would have been no reason to add references to the language. If you want to assign them to refer to different objects, you need to user pointers, not references. Generally speaking, I'd use a reference when you can, and a pointer if you have to. At least IMO, a reference as a class member is usually highly suspect at best (e.g., it means you can't assign objects of that type). Bottom: if you want what a reference does, by all means use a reference -- but complaining because a reference isn't a pointer doesn't seem (at least to me) to make much sense.

Jerry Coffin
Another serious problem with the question is that Jim equates pointers and dynamic allocation. But pointers to static, global, and automatic variables work just fine too.
Ben Voigt
@Ben: I can't blame you for skipping over part of such a long answer, but I did mention that in the paragraph (about halfway through) that starts with "Note, however, ...".
Jerry Coffin
@Ben, I didn't equate them. My point was just this: I am not aware of any way of achieving dynamic allocation without pointers, raw or smart.
Jim
@Jerry, As for `operator<<` part, I am well aware of it. I just made up a quick and dirty example to illustrate my point.
Jim
@Jerry, Okay that was a bad example (my `Student` example). I'll find some other example and include that in my post.
Jim
@Jim: sorry, but you're just plain wrong on that one.
Jerry Coffin
@Jim: What's this, if not a claim that pointers are only used for dynamic polymorphism: "Now I am aware that I can use dynamic allocation (i.e. pointers) to achieve subtyping polymorphism in this case. However static allocation is more common in C++. Pointers are used as last resort (I remember having read it in some other thread here).So I find it difficult it difficult to reconcile the Good Practices that recommend static allocation over dynamic allocation (this is what I meant when I said C++ is value oriented) with subtyping polymorphism."
Ben Voigt
@Jerry: Sorry for my bad example. Anyway your post does answer my question and I am feeling a bit lazy to think of some other example now so I am marking this as correct answer. (If some better answer comes up, I'll move over the green tick to it.) Thank you.
Jim
"As far as type inference goes, C++ has it for function templates"This is not type inference, you're talking about type deduction from function arguments of an invoked template function, that is not the same thing as type inference of function types.Haskell is totally type inferred language, if you do not provide a function type for a function definition or lambda expression then the compiler will infer it, the compiler can even infer parametric types and even type constraints. This is impossible in C++ and C++0x.
snk_kid
@Ben: I don't want to argue with you but I didn't really intend to mean what you interpreted. Matter closed.
Jim
@snk_kid: +1, I just love Haskell. I am really dying to get out of my C++ job. :(
Jim
@snk_kid: yes and no. Yes, in C++ it's type deduction, but the effect is similar to type inference in Haskell -- specifically, you do *not* have to specify the type of (for example) a parameter, but (unlike something like Lisp) the compiler figures out and *enforces* the constraints of that type.
Jerry Coffin
@Jerry: Lisp is dynamically typed. No question of type constraints there.
Jim
@Jim: Right -- I was just pointing out that in this respect, C++ and Haskell as basically similar -- in both cases, the compiler is figuring out (deducing or inferring, as you prefer) a type, the constraints of which are then enforced. The primary difference is that a "normal" Haskell function is similar to a C++ function template that includes a separate template parameter for each function parameter.
Jerry Coffin
@Jerry: I don't think you understand what I wrote, they are not "basically similar", type deduction is a very limited form of type inference. There is no unification process going on in C++'s type system, you have to explicitly write the type of template function in C++ you have to explicitly state it's a template function. In Haskell you can define a function with no type signature and the compiler can infer what it's type is, it can infer it's a polymorphic function with a type constraint (type-class). Template function type deduction is nothing like that.
snk_kid