AFAIK:
C++ provides three different types of polymorphism.
If anybody says CUT to these people
The Surgeon
The Hair Stylist
The Actor
What will happen?
The Surgeon would begin to make an incision.
The Hair Stylist would begin to cut someone's hair.
The Actor would abruptly stop acting out of the current scene, awaiting directorial guidance.
So above representation shows What is polymorphism (same name, different behavior) in OOP.
If you are going for an interview and interviewer asks you tell/show a live example for polymorphism in the same room we are sitting at, say-
Answer - Door / Windows
Wondering How?
Through Door / Window - a person can come, air can come, light can come, rain can come, etc.
i.e. One form different behavior(Polymorphism).
To understand it better and in a simple manner I used above example.. If you need reference for code follow above answers.
As to ad-hoc polymorphism, it means function overloading or operator overloading. Check out here:
http://en.wikipedia.org/wiki/Ad-hoc_polymorphism
As to parametric polymorphism, template functions can also be counted in because they don't necessarily take in parameters of FIXED types. For example, one function can sort array of integers and it can also sort array of strings, etc.
http://en.wikipedia.org/wiki/Parametric_polymorphism
Here is a basic example using Polymorphic classes
#include <iostream>
class Animal{
public:
Animal(const char* Name) : name_(Name){/* Add any method you would like to perform here*/
virtual void Speak(){
std::cout << "I am an animal called " << name_ << std::endl;
}
const char* name_;
};
class Dog : public Animal{
public:
Dog(const char* Name) : Animal(Name) {/*...*/}
void Speak(){
std::cout << "I am a dog called " << name_ << std::endl;
}
};
int main(void){
Animal Bob("Bob");
Dog Steve("Steve");
Bob.Speak();
Steve.Speak();
//return (0);
}
In C++, the important distinction is run-time vs. compile-time binding. Ad-hoc vs. parametric doesn't really help, as I'll explain later.
|----------------------+--------------|
| Form | Resolved at |
|----------------------+--------------|
| function overloading | compile-time |
| operator overloading | compile-time |
| templates | compile-time |
| virtual methods | run-time |
|----------------------+--------------|
Note - run-time polymorphism may still be resolved at compile-time, but that's just optimization. Needing to support run-time resolution efficiently, and trading off against other issues, is part of what led to virtual functions being what they are. And that's really key for all forms of polymorphism in C++ - each arises from different sets of trade-offs made in a different context.
Function overloading and operator overloading are the same thing in every way that matters. The names and the syntax for using them doesn't affect polymorphism.
Templates allow you to specify lots of function overloads at once.
There's another set of names for the same resolution-time idea...
|---------------+--------------|
| early binding | compile-time |
| late binding | run-time |
|---------------+--------------|
These names are more associated with OOP, so it's a bit odd to say that a template or other non-member function uses early binding.
To better understand the relationship between virtual functions and function overloading, it's also useful to understand the difference between "single dispatch" and "multiple dispatch". The idea can be understood as a progression...
There's obviously more to OOP than an excuse to nominate one parameter as special, but that is one part of it. And relating back to what I said about trade-offs - single dispatch is quite easy to do efficiently (the usual implementation is called "virtual tables"). Multiple dispatch is more awkward, not just in terms of efficiency, but also for separate compilation. If you're curious, you might look up "the expression problem".
Just as it's a bit odd to use the term "early binding" for non-member functions, it's a bit odd to use the terms "single dispatch" and "multiple dispatch" where polymorphism is resolved at compile-time. Usually, C++ is considered not to have multiple dispatch, which is considered a particular kind of run-time resolution. However, function overloading can be seen as multiple-dispatch done at compile-time.
Getting back to parametric vs. ad-hoc polymorphism, these terms are more popular in functional programming, and they don't quite work in C++. Even so...
Parametric polymorphism means that you have types as parameters, and the exact same code is used irrespective of what type you use for those parameters.
Ad-hoc polymorphism is ad-hoc in the sense that you provide different code depending on the particular types.
Overloading and virtual functions are both examples of ad-hoc polymorphism.
Again, there's some synonyms...
|------------+---------------|
| parametric | unconstrained |
| ad-hoc | constrained |
|------------+---------------|
Except these aren't quite synonyms, though they're commonly treated as though they were, and that's where confusion is likely to arise in C++.
The reasoning behind treating these as synonyms is that by constraining polymorphism to particular classes of types, it becomes possible to use operations specific to those classes of types. The word "classes" here can be interpreted in the OOP sense, but really just refers to (usually named) sets of types that share certain operations.
So parametric polymorphism is usually taken (at least by default) to imply unconstrained polymorphism. Because the same code is used irrespective of the type parameters, the only supportable operations are those that work for all types. By leaving the set of types unconstrained, you severely limit the set of operations you can apply to those types.
In e.g. Haskell, you can have...
myfunc1 :: Bool -> a -> a -> a
myfunc1 c x y = if c then x else y
The a
here is an unconstrained polymorphic type. It could be anything, so there's not much we can do with values of that type.
myfunc2 :: Num a => a -> a
myfunc2 x = x + 3
Here, a
is constrained to be a member of the Num
class - types that act like numbers. That constraint allows you to do number-ish things with those values, such as add them. Even the 3
is polymorphic - type inference figures out that you mean the 3
of type a
.
I think of this as constrained parametric polymorphism. There's only one implementation, but it can only be applied in constrained cases. The ad-hoc aspect is the choice of which +
and 3
to use. Each "instance" of Num
has it's own distinct implementation of these. So even in Haskell "parametric" and "unconstrained" aren't really synonyms - don't blame me, it's not my fault!
In C++, both overloading and virtual functions are ad-hoc polymorphism. The definition of ad-hoc polymorphism doesn't care whether the implementation is selected at run-time or compile-time.
C++ gets very close to parametric polymorphism with templates if every template parameter has type typename
. There are type parameters, and there's a single implementation no matter which types are used. However, the "Substitution Failure Is Not An Error" rule means that implicit constraints arise as a result of using operations within the template. Additional complications include template specialization for providing alternative templates - different (ad-hoc) implementations.
So in a way C++ has parametric polymorphism, but it's implicitly constrained and could be overridden by ad-hoc alternatives - ie this classification doesn't really work for C++.
Polymorphism means many forms as such it is used for an operator to act differently under different instances. Polymorphism is used to implement inheritance. For ex, we have defined a fn draw () for a class shape then the draw fn can be implemented for drawing circle, box, triangle and other shapes. ( which are objects of the class shape)
This may not be of any help, but I made this to introduce my friends to programming by giving out defined functions, like START
, and END
for the main function so it was not too daunting (they only used the main.cpp file). It contains Polymorphic classes and structs, templates, vectors, arrays, preproccessor directives, friendship, operators and pointers (all of which you should probably know before attempting polymorphism):
Note: It is not finished, but you can get the idea
main.cpp
#include "main.h"
#define ON_ERROR_CLEAR_SCREEN false
START
Library MyLibrary;
Book MyBook("My Book", "Me");
MyBook.Summarize();
MyBook += "Hello World";
MyBook += "HI";
MyBook.EditAuthor("Joe");
MyBook.EditName("Hello Book");
MyBook.Summarize();
FixedBookCollection<FairyTale> FBooks("Fairytale Books");
FairyTale MyTale("Tale", "Joe");
FBooks += MyTale;
BookCollection E("E");
MyLibrary += E;
MyLibrary += FBooks;
MyLibrary.Summarize();
MyLibrary -= FBooks;
MyLibrary.Summarize();
FixedSizeBookCollection<5> Collection("My Fixed Size Collection");
/* Extension Work */ Book* Duplicate = MyLibrary.DuplicateBook(&MyBook);
/* Extension Work */ Duplicate->Summarize();
END
main.h
#include <iostream>
#include <sstream>
#include <vector>
#include <string>
#include <type_traits>
#include <array>
#ifndef __cplusplus
#error Not C++
#endif
#define START int main(void)try{
#define END GET_ENTER_EXIT return(0);}catch(const std::exception& e){if(ON_ERROR_CLEAR_SCREEN){system("cls");}std::cerr << "Error: " << e.what() << std::endl; GET_ENTER_EXIT return (1);}
#define GET_ENTER_EXIT std::cout << "Press enter to exit" << std::endl; getchar();
class Book;
class Library;
typedef std::vector<const Book*> Books;
bool sContains(const std::string s, const char c){
return (s.find(c) != std::string::npos);
}
bool approve(std::string s){
return (!sContains(s, '#') && !sContains(s, '%') && !sContains(s, '~'));
}
template <class C> bool isBook(){
return (typeid(C) == typeid(Book) || std::is_base_of<Book, C>());
}
template<class ClassToDuplicate> class DuplicatableClass{
public:
ClassToDuplicate* Duplicate(ClassToDuplicate ToDuplicate){
return new ClassToDuplicate(ToDuplicate);
}
};
class Book : private DuplicatableClass<Book>{
friend class Library;
friend struct BookCollection;
public:
Book(const char* Name, const char* Author) : name_(Name), author_(Author){}
void operator+=(const char* Page){
pages_.push_back(Page);
}
void EditAuthor(const char* AuthorName){
if(approve(AuthorName)){
author_ = AuthorName;
}
else{
std::ostringstream errorMessage;
errorMessage << "The author of the book " << name_ << " could not be changed as it was not approved";
throw std::exception(errorMessage.str().c_str());
}
}
void EditName(const char* Name){
if(approve(Name)){
name_ = Name;
}
else{
std::ostringstream errorMessage;
errorMessage << "The name of the book " << name_ << " could not be changed as it was not approved";
throw std::exception(errorMessage.str().c_str());
}
}
virtual void Summarize(){
std::cout << "Book called " << name_ << "; written by " << author_ << ". Contains "
<< pages_.size() << ((pages_.size() == 1) ? " page:" : ((pages_.size() > 0) ? " pages:" : " pages")) << std::endl;
if(pages_.size() > 0){
ListPages(std::cout);
}
}
private:
std::vector<const char*> pages_;
const char* name_;
const char* author_;
void ListPages(std::ostream& output){
for(int i = 0; i < pages_.size(); ++i){
output << pages_[i] << std::endl;
}
}
};
class FairyTale : public Book{
public:
FairyTale(const char* Name, const char* Author) : Book(Name, Author){}
};
struct BookCollection{
friend class Library;
BookCollection(const char* Name) : name_(Name){}
virtual void operator+=(const Book& Book)try{
Collection.push_back(&Book);
}catch(const std::exception& e){
std::ostringstream errorMessage;
errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
throw std::exception(errorMessage.str().c_str());
}
virtual void operator-=(const Book& Book){
for(int i = 0; i < Collection.size(); ++i){
if(Collection[i] == &Book){
Collection.erase(Collection.begin() + i);
return;
}
}
std::ostringstream errorMessage;
errorMessage << "The Book " << Book.name_ << " was not found, and therefore cannot be erased";
throw std::exception(errorMessage.str().c_str());
}
private:
const char* name_;
Books Collection;
};
template<class FixedType> struct FixedBookCollection : public BookCollection{
FixedBookCollection(const char* Name) : BookCollection(Name){
if(!isBook<FixedType>()){
std::ostringstream errorMessage;
errorMessage << "The type " << typeid(FixedType).name() << " cannot be initialized as a FixedBookCollection";
throw std::exception(errorMessage.str().c_str());
delete this;
}
}
void operator+=(const FixedType& Book)try{
Collection.push_back(&Book);
}catch(const std::exception& e){
std::ostringstream errorMessage;
errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
throw std::exception(errorMessage.str().c_str());
}
void operator-=(const FixedType& Book){
for(int i = 0; i < Collection.size(); ++i){
if(Collection[i] == &Book){
Collection.erase(Collection.begin() + i);
return;
}
}
std::ostringstream errorMessage;
errorMessage << "The Book " << Book.name_ << " was not found, and therefore cannot be erased";
throw std::exception(errorMessage.str().c_str());
}
private:
std::vector<const FixedType*> Collection;
};
template<size_t Size> struct FixedSizeBookCollection : private std::array<const Book*, Size>{
FixedSizeBookCollection(const char* Name) : name_(Name){ if(Size < 1){ throw std::exception("A fixed size book collection cannot be smaller than 1"); currentPos = 0; } }
void operator+=(const Book& Book)try{
if(currentPos + 1 > Size){
std::ostringstream errorMessage;
errorMessage << "The FixedSizeBookCollection " << name_ << "'s size capacity has been overfilled";
throw std::exception(errorMessage.str().c_str());
}
this->at(currentPos++) = &Book;
}catch(const std::exception& e){
std::ostringstream errorMessage;
errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
throw std::exception(errorMessage.str().c_str());
}
private:
const char* name_;
int currentPos;
};
class Library : private std::vector<const BookCollection*>{
public:
void operator+=(const BookCollection& Collection){
for(int i = 0; i < size(); ++i){
if((*this)[i] == &Collection){
std::ostringstream errorMessage;
errorMessage << "The BookCollection " << Collection.name_ << " was already in the library, and therefore cannot be added";
throw std::exception(errorMessage.str().c_str());
}
}
push_back(&Collection);
}
void operator-=(const BookCollection& Collection){
for(int i = 0; i < size(); ++i){
if((*this)[i] == &Collection){
erase(begin() + i);
return;
}
}
std::ostringstream errorMessage;
errorMessage << "The BookCollection " << Collection.name_ << " was not found, and therefore cannot be erased";
throw std::exception(errorMessage.str().c_str());
}
Book* DuplicateBook(Book* Book)const{
return (Book->Duplicate(*Book));
}
void Summarize(){
std::cout << "Library, containing " << size() << ((size() == 1) ? " book collection:" : ((size() > 0) ? " book collections:" : " book collections")) << std::endl;
if(size() > 0){
for(int i = 0; i < size(); ++i){
std::cout << (*this)[i]->name_ << std::endl;
}
}
}
};