136

AFAIK:

C++ provides three different types of polymorphism.

  • Virtual functions
  • Function name overloading
  • Operator overloading

In addition to the above three types of polymorphism, there exist other kinds of polymorphism:

  • run-time
  • compile-time
  • ad-hoc polymorphism
  • parametric polymorphism

I know that runtime polymorphism can be achieved by virtual functions and static polymorphism can be achieved by template functions

But for the other two

  • ad-hoc polymorphism
  • parametric polymorphism the website says,

ad-hoc polymorphism:

If the range of actual types that can be used is finite and the combinations must be individually specified prior to use, this is called ad-hoc polymorphism.

parametric polymorphism:

If all code is written without mention of any specific type and thus can be used transparently with any number of new types it is called parametric polymorphism.

I can hardly understand them :(

can anyone explain them both if possible with an example? I hope the answers to this questions would be helpful for many new passouts from their colleges.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Vijay
  • 65,327
  • 90
  • 227
  • 319
  • 31
    Actually, C++ has *four* kinds of polymorphism: parametric (genericity via templates in C++), inclusion (subtyping via virtual methods in C++), overloading and coercion (implicit conversions). Conceptionally, there is little distinction between function overloading and operator overloading. – fredoverflow May 02 '11 at 07:41
  • So it seems that the website i mentioned is misleading many..am i correct? – Vijay May 02 '11 at 07:47
  • @zombie: that website touches on a lot of good concepts, but isn't precise and consistent in its use of terminology (for example, once it starts talking about virtual dispatch / runtime polymorphism, it makes a lot statements about polymorphism that are wrong in general but true for virtual dispatch). If you already understand the subject, you can relate to what's being said and mentally insert the necessary caveats, but it's hard to get there by reading the site.... – Tony Delroy May 02 '11 at 09:05
  • Some terms are near-synonyms, or more related to but more restricted than other terms. For example the term "ad-hoc polymorphism" is mostly used in Haskell in my experience, yet "virtual functions" is very closely related. The minor difference is that "virtual functions" is an object-oriented term referring to member functions with "late binding". "Multiple dispatch" is also a kind of ad-hoc polymorphism. And as FredOverflow says, both operator and function overloading are basically the same thing. –  May 02 '11 at 09:29
  • I fixed your formatting for you. Please read the help available to the right of the edit pane. Someone with >200 questions and >3k should know this basic stuff. Also, you might want to buy a new keyboard. This one's shift key seems to be failing intermittedly. Oh, and: ___there is no such thing as a "template function"___ in C++. There are, however, ___function templates___. – sbi May 02 '11 at 09:42
  • @sbi,people face this question in interviews,"what is the difference between template function and function template"?if there is no such thing then why do they ask? http://www.comeaucomputing.com/techtalk/templates/#terms – Vijay May 02 '11 at 10:18
  • @zombie: In that article, Greg explains that what's often called a "template function" is a "function template". He says a function template instance _could_ be called a "template function". Did you use the term in that way? I would advise to not to use it at all. It is misleading because it's been used wrongly so much. – sbi May 02 '11 at 12:41
  • ok i agree i just mentioned the web site because of your comment "there is no such thing as a "template function" – Vijay May 02 '11 at 15:47
  • @zombie: I hadn't known Greg used it, and it's the first time I heard it used in that way, which is why I believed there not to be such a beast. – sbi May 02 '11 at 16:05
  • @sbi - just clarifying - are you saying that the "function template" is the uninstantiated template, whereas the "template function" is (best avoided term) the instantiated function? Confusion seems understandable to me. By English grammar, there is usually a "x y" -> "y of the x" substitution pattern for nouns used together, so "function template" -> "template of the function" makes sense, but "template function" -> "function of the template" doesn't really seem seem to imply "instantiated" to me. If anything, it uses a different meaning of "function" to imply "purpose of the template". –  May 05 '11 at 10:19
  • @Steve: A _function template_ is a template from which you can generate functions (much like a _tablecloth_ is a cloth you wrap over a table and _baby oil_ is oil made from babies `:)`). Now, Greg says, that a _template function_ is a function that had been generated from a template. I'd say that's a pretty good term, except just about everybody else who is using the term _template function_ is using it wrongly to refer to a _function template_. – sbi May 05 '11 at 10:56
  • @sbi - to remove the pun from your pun, baby oil is oil made *for* babies. The x y -> y of the x pattern I remember from school is presumably meant to be a vague generalisation, where the "of the" should generally be replaced with something more specific. Your claim of ambiguity seems strong to me, but outside of puns, we don't say that "baby oil" is ambiguous - there is a commonly accepted meaning, so the multi-word term is effectively a word in its own right. Is there a commonly accepted meaning for "template function"? *How* commonly is "template function" used wrongly? –  May 06 '11 at 22:40
  • @Steve: I know why it's called baby oil, and it was just a joke, and doesn't invalidate at all what I was saying. "Class template" is not ambiguous ("template for a class" would seems right), and it's the proper term. As I wrote Greg's terminology was the first time I came across "template class" where it did not (wrongly) refer to a _class template_, and I hear it called "template class" quite often. – sbi May 06 '11 at 23:05
  • @sbi - sorry if that came across wrong. If I just commented "How commonly is "template function" used wrongly?" it wouldn't be clear what I was getting at. I know you were making a joke, and for the most part I wasn't intending to invalidate what you said - I was just building up to my question and suggestion. On the but-human-languages-are-defined-by-common-usage aspect, that's just an obsessive mantra of mine - a recurring joke (though with a point behind it) that you shouldn't take personally. –  May 07 '11 at 00:08
  • @Steve: Now that I read it, I see that my reply might have come across much harsher than it was meant at the time. I apologize. Anyway, it's used wrongly _very_ often, so often that it's probably used wrong more often than right. But I still maintain that it's wrong. First for pedantic reasons (every C++ is a pedant), but also for pedagogical reasons: the term "template class", when applied to _class template_, wrongly implies that the thing is a class, while it is not, often confusing newbies to the point where they try to use a template where a class is required. – sbi May 08 '11 at 06:32
  • @sbi: just for the sake of discussion - and worth all of 2c IMHO - another possible reason some people may graviate towards leading with "template" is that in source code the keywords are encountered in that order: (template ... function) and (template ... class). – Tony Delroy Aug 22 '12 at 05:16

7 Answers7

225

Understanding of / requirements for polymorphism

To understand polymorphism - as the term is used in Computing Science - it helps to start from a simple test for and definition of it. Consider:

    Type1 x;
    Type2 y;

    f(x);
    f(y);

Here, f() is to perform some operation and is being given values x and y as inputs.

To exhibit polymorphism, f() must be able to operate with values of at least two distinct types (e.g. int and double), finding and executing distinct type-appropriate code.


C++ mechanisms for polymorphism

Explicit programmer-specified polymorphism

You can write f() such that it can operate on multiple types in any of the following ways:

  • Preprocessing:

    #define f(X) ((X) += 2)
    // (note: in real code, use a longer uppercase name for a macro!)
    
  • Overloading:

    void f(int& x)    { x += 2; }
    
    void f(double& x) { x += 2; }
    
  • Templates:

    template <typename T>
    void f(T& x) { x += 2; }
    
  • Virtual dispatch:

    struct Base { virtual Base& operator+=(int) = 0; };
    
    struct X : Base
    {
        X(int n) : n_(n) { }
        X& operator+=(int n) { n_ += n; return *this; }
        int n_;
    };
    
    struct Y : Base
    {
        Y(double n) : n_(n) { }
        Y& operator+=(int n) { n_ += n; return *this; }
        double n_;
    };
    
    void f(Base& x) { x += 2; } // run-time polymorphic dispatch
    

Other related mechanisms

Compiler-provided polymorphism for builtin types, Standard conversions, and casting/coercion are discussed later for completeness as:

  • they're commonly intuitively understood anyway (warranting a "oh, that" reaction),
  • they impact the threshold in requiring, and seamlessness in using, the above mechanisms, and
  • explanation is a fiddly distraction from more important concepts.

Terminology

Further categorisation

Given the polymorphic mechanisms above, we can categorise them in various ways:

  • When is the polymorphic type-specific code selected?

    • Run time means the compiler must generate code for all the types the program might handle while running, and at run-time the correct code is selected (virtual dispatch)
    • Compile time means the choice of type-specific code is made during compilation. A consequence of this: say a program only called f above with int arguments - depending on the polymorphic mechanism used and inlining choices the compiler might avoid generating any code for f(double), or generated code might be thrown away at some point in compilation or linking. (all mechanisms above except virtual dispatch)

  • Which types are supported?

    • Ad-hoc meaning you provide explicit code to support each type (e.g. overloading, template specialisation); you explicitly add support "for this" (as per ad hoc's meaning) type, some other "this", and maybe "that" too ;-).
    • Parametric meaning you can just try to use the function for various parameter types without specifically doing anything to enable its support for them (e.g. templates, macros). An object with functions/operators that act like the template/macro expects1 is all that template/macro needs to do its job, with the exact type being irrelevant. The "concepts" introduced by C++20 express and enforce such expectations - see cppreference page here.

      • Parametric polymorphism provides duck typing - a concept attributed to James Whitcomb Riley who apparently said "When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.".

        template <typename Duck>
        void do_ducky_stuff(const Duck& x) { x.walk().swim().quack(); }
        
        do_ducky_stuff(Vilified_Cygnet());
        
    • Subtype (aka inclusion) polymorphism allows you to work on new types without updating the algorithm/function, but they must be derived from the same base class (virtual dispatch)

1 - Templates are extremely flexible. SFINAE (see also std::enable_if) effectively allows several sets of expectations for parametric polymorphism. For example, you might encode that when the type of data you're processing has a .size() member you'll use one function, otherwise another function that doesn't need .size() (but presumably suffers in some way - e.g. using the slower strlen() or not printing as useful a message in the log). You can also specify ad-hoc behaviours when the template is instantiated with specific parameters, either leaving some parameters parametric (partial template specialisation) or not (full specialisation).

"Polymorphic"

Alf Steinbach comments that in the C++ Standard polymorphic only refers to run-time polymorphism using virtual dispatch. General Comp. Sci. meaning is more inclusive, as per C++ creator Bjarne Stroustrup's glossary (http://www.stroustrup.com/glossary.html):

polymorphism - providing a single interface to entities of different types. Virtual functions provide dynamic (run-time) polymorphism through an interface provided by a base class. Overloaded functions and templates provide static (compile-time) polymorphism. TC++PL 12.2.6, 13.6.1, D&E 2.9.

This answer - like the question - relates C++ features to the Comp. Sci. terminology.

Discussion

With the C++ Standard using a narrower definition of "polymorphism" than the Comp. Sci. community, to ensure mutual understanding for your audience consider...

  • using unambiguous terminology ("can we make this code reusable for other types?" or "can we use virtual dispatch?" rather than "can we make this code polymorphic?"), and/or
  • clearly defining your terminology.

Still, what's crucial to being a great C++ programmer is understanding what polymorphism's really doing for you...

    letting you write "algorithmic" code once and then apply it to many types of data

...and then be very aware of how different polymorphic mechanisms match your actual needs.

Run-time polymorphism suits:

  • input processed by factory methods and spat out as an heterogeneous object collection handled via Base*s,
  • implementation chosen at runtime based on config files, command line switches, UI settings etc.,
  • implementation varied at runtime, such as for a state machine pattern.

When there's not a clear driver for run-time polymorphism, compile-time options are often preferable. Consider:

  • the compile-what's-called aspect of templated classes is preferable to fat interfaces failing at runtime
  • SFINAE
  • CRTP
  • optimisations (many including inlining and dead code elimination, loop unrolling, static stack-based arrays vs heap)
  • __FILE__, __LINE__, string literal concatenation and other unique capabilities of macros (which remain evil ;-))
  • templates and macros test semantic usage is supported, but don't artificially restrict how that support is provided (as virtual dispatch tends to by requiring exactly matching member function overrides)

Other mechanisms supporting polymorphism

As promised, for completeness several peripheral topics are covered:

  • compiler-provided overloads
  • conversions
  • casts/coercion

This answer concludes with a discussion of how the above combine to empower and simplify polymorphic code - especially parametric polymorphism (templates and macros).

Mechanisms for mapping to type-specific operations

> Implicit compiler-provided overloads

Conceptually, the compiler overloads many operators for builtin types. It's not conceptually different from user-specified overloading, but is listed as it's easily overlooked. For example, you can add to ints and doubles using the same notation x += 2 and the compiler produces:

  • type-specific CPU instructions
  • a result of the same type.

Overloading then seamlessly extends to user-defined types:

std::string x;
int y = 0;

x += 'c';
y += 'c';

Compiler-provided overloads for basic types is common in high-level (3GL+) computer languages, and explicit discussion of polymorphism generally implies something more. (2GLs - assembly languages - often require the programmer to explicitly use different mnemonics for different types.)

> Standard conversions

The C++ Standard's fourth section describes Standard conversions.

The first point summarises nicely (from an old draft - hopefully still substantially correct):

-1- Standard conversions are implicit conversions defined for built-in types. Clause conv enumerates the full set of such conversions. A standard conversion sequence is a sequence of standard conversions in the following order:

  • Zero or one conversion from the following set: lvalue-to-rvalue conversion, array-to-pointer conversion, and function-to-pointer conversion.

  • Zero or one conversion from the following set: integral promotions, floating point promotion, integral conversions, floating point conversions, floating-integral conversions, pointer conversions, pointer to member conversions, and boolean conversions.

  • Zero or one qualification conversion.

[Note: a standard conversion sequence can be empty, i.e., it can consist of no conversions. ] A standard conversion sequence will be applied to an expression if necessary to convert it to a required destination type.

These conversions allow code such as:

double a(double x) { return x + 2; }

a(3.14);
a(42);

Applying the earlier test:

To be polymorphic, [a()] must be able to operate with values of at least two distinct types (e.g. int and double), finding and executing type-appropriate code.

a() itself runs code specifically for double and is therefore not polymorphic.

But, in the second call to a() the compiler knows to generate type-appropriate code for a "floating point promotion" (Standard §4) to convert 42 to 42.0. That extra code is in the calling function. We'll discuss the significance of this in the conclusion.

> Coercion, casts, implicit constructors

These mechanisms allow user-defined classes to specify behaviours akin to builtin types' Standard conversions. Let's have a look:

int a, b;

if (std::cin >> a >> b)
    f(a, b);

Here, the object std::cin is evaluated in a boolean context, with the help of a conversion operator. This can be conceptually grouped with "integral promotions" et al from the Standard conversions in the topic above.

Implicit constructors effectively do the same thing, but are controlled by the cast-to type:

f(const std::string& x);
f("hello");  // invokes `std::string::string(const char*)`

Implications of compiler-provided overloads, conversions and coercion

Consider:

void f()
{
    typedef int Amount;
    Amount x = 13;
    x /= 2;
    std::cout << x * 1.1;
}

If we want the amount x to be treated as a real number during the division (i.e. be 6.5 rather than rounded down to 6), we only need change to typedef double Amount.

That's nice, but it wouldn't have been too much work to make the code explicitly "type correct":

void f()                               void f()
{                                      {
    typedef int Amount;                    typedef double Amount;
    Amount x = 13;                         Amount x = 13.0;
    x /= 2;                                x /= 2.0;
    std::cout << double(x) * 1.1;          std::cout << x * 1.1;
}                                      }

But, consider that we can transform the first version into a template:

template <typename Amount>
void f()
{
    Amount x = 13;
    x /= 2;
    std::cout << x * 1.1;
}

It's due to those little "convenience features" that it can be so easily instantiated for either int or double and work as intended. Without these features, we'd need explicit casts, type traits and/or policy classes, some verbose, error-prone mess like:

template <typename Amount, typename Policy>
void f()
{
    Amount x = Policy::thirteen;
    x /= static_cast<Amount>(2);
    std::cout << traits<Amount>::to_double(x) * 1.1;
}

So, compiler-provided operator overloading for builtin types, Standard conversions, casting / coercion / implicit constructors - they all contribute subtle support for polymorphism. From the definition at the top of this answer, they address "finding and executing type-appropriate code" by mapping:

  • "away" from parameter types

    • from the many data types polymorphic algorithmic code handles

    • to code written for a (potentially lesser) number of (the same or other) types.

  • "to" parametric types from values of constant type

They do not establish polymorphic contexts by themselves, but do help empower/simplify code inside such contexts.

You may feel cheated... it doesn't seem like much. The significance is that in parametric polymorphic contexts (i.e. inside templates or macros), we're trying to support an arbitrarily large range of types but often want to express operations on them in terms of other functions, literals and operations that were designed for a small set of types. It reduces the need to create near-identical functions or data on a per-type basis when the operation/value is logically the same. These features cooperate to add an attitude of "best effort", doing what's intuitively expected by using the limited available functions and data and only stopping with an error when there's real ambiguity.

This helps limit the need for polymorphic code supporting polymorphic code, drawing a tighter net around the use of polymorphism so localised use doesn't force widespread use, and making the benefits of polymorphism available as needed without imposing the costs of having to expose implementation at compile time, have multiple copies of the same logical function in the object code to support the used types, and in doing virtual dispatch as opposed to inlining or at least compile-time resolved calls. As is typical in C++, the programmer is given a lot of freedom to control the boundaries within which polymorphism is used.

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
  • 1
    -1 Great answer except for the terminology discussion. The C++ standard **defines** the term "polymorphic" in §1.8/1, there referring to section 10.3 about virtual functions. So there is no wiggle-room, no room for discussion, no room for personal opinion: in the context of standard C++ that term is defined once and for all. And it does play an in-practice rôle. For example, §5.2.7/6 about `dynamic_cast` requires a "pointer to or an lvalue of a polymorphic type". Cheers & hth., – Cheers and hth. - Alf Jun 27 '11 at 06:09
  • @Alf: great reference - though I think your perspective is too narrow. It's very clear from the question listing overloading, ad-hoc and parametric polymorphism etc. that the an answer should relate C++'s capabilities to the general Comp. Sci. meaning of the terms. Indeed, Stroustrup's glossary says "polymorphism - providing a single interface to entities of different types. virtual functions provide dynamic (run-time) polymorphism through an interface provided by a base class. Overloaded functions and templates provide static (compile-time) polymorphism. TC++PL 12.2.6, 13.6.1, D&E 2.9." – Tony Delroy Jun 27 '11 at 07:22
  • @Tony: it's not the main thrust of your answer is wrong. it's ok, it's great. it's just that wrt. terminology you got it backwards: the formal academic terminology is the narrow one defined by the Holy International Standard, and the informal rough terminology where people may mean slightly different things, is the one mainly used in this question and answer. Cheers & hth., – Cheers and hth. - Alf Jun 27 '11 at 07:48
  • @Alf: I wish the answer were great - "Other mechanisms" needs to be rewritten in a fifth of the lines, and I'm contemplating/drafting a more concrete features-and-implications contrasting of the polymorphic mechanisms. Anyway, my understanding is that the formal academic *exclusively-C++-focused* meaning may be narrow, but the formal academic general Comp. Sci. meaning is not, as evidenced by Stroustrup's glossary. We need something definitive - e.g. definition from Knuth - no luck yet googling. I appreciate you're a C++ guru, but can you point at pertinent evidence on this specifically? – Tony Delroy Jun 27 '11 at 08:13
  • @Alf: Not that it's definitive, but just by way of showing I'm not completely alone ;-) http://en.wikipedia.org/wiki/Polymorphism_(computer_science) – Tony Delroy Jun 27 '11 at 08:21
  • @Tony: I already pointed: the standard formally defines "polymorphic" in §1.8/1 (when a term is being defined inline, it's set in italics). For a computer programming language *you can not get any more definitive or any higher authority* than its International Standard. If, for example, Bjarne says something contradicting the standard (and he has), then it's the standard that rules, and Bjarne who must bow -- as must we all. But yes in general the more general concept-based terminology is used. Since it's not formally defined, it is *informal*. Don't confuse academic with formal. Is that it? – Cheers and hth. - Alf Jun 27 '11 at 08:44
  • @Alf: the first issue here is the dominant context of the question: that's of a person armed with some Comp. Sci. terminology asking how C++ features relate to those terms. They're not asking what the terms mean in the context of the C++ Standard. Such terms aren't absolutes in the scientific sense, they're notational conveniences - see http://stackoverflow.com/questions/5387412/can-any-one-explain-the-difference/5387911#5387911 for an example. As such, if these meanings differ it is noteworthy but not more. – Tony Delroy Jun 27 '11 at 09:05
  • 1
    @Alf: secondly, I'm confident that polymorphism is *formally* defined in any decent general Comp. Sci. book in a (timeless, stable) way compatible with my usage (and Stroustrup's). The Wikipedia article links a few academic publications that define it that way: "Polymorphic functions are functions whose operands (actual parameters) can have more than one type. Polymorphic types are types whose operations are applicable to values of more than one type." (from http://lucacardelli.name/Papers/OnUnderstanding.A4.pdf). So, the question is "who speaks for Comp. Sci"...? – Tony Delroy Jun 27 '11 at 09:09
  • @Alf: I don't think there's any significant dispute inside Comp. Sci. for that to be an issue, though certainly in narrower context like discussion of languages that only provide run-time polymorphism, the C++ Standard, or of OO programming, a narrower view is taken. I've never heard of most of the academics linked from Wikipedia, so seeing a formal definition from someone like Knuth would be nice. But, the C++ Standard's basically an engineering contract... it uses terms in a way that communicates requirements, not for their scientific accuracy. It's not authoritative in this way. – Tony Delroy Jun 27 '11 at 09:27
  • Duck-typing is (implicitly) constrained polymorphism - the function only works for types that support quacking. The SFINAE rule in C++ means that templates are implicitly constrained that way. "Parametric" and "unconstrained" are usually used as synonyms WRT polymorphism. Strictly, even in a language like Haskell, the constrained parametric polymorphism is separate from the ad-hoc polymorphism (the ad-hoc is in the class instances) but the tendency is to treat "unconstrained" and "parametric" as synonyms and, because the provisos are different, things get confusing applying those terms in C++. –  Nov 17 '13 at 09:37
  • @Steve314: sorry, I'm struggling to follow. Are you saying that duck typing differs from parametric polymorphism in that it requires either support for constraints (like C++0x's Concepts promised) or type-property based overloading resolution ala SFINAE? Is the key distinction that some test for "types that support quacking" guides choice or customisation of implementation before the template instantiation or macro expansion gets a fatal compiler error? My google-foo's not turning up precedent for this aspect of duck typing - do you know of any definition / articles about this? – Tony Delroy Nov 18 '13 at 02:27
  • @Tony D - It's just an observation - before reading this answer, I never considered duck typing relative to the parametric/ad-hoc classification. Working in Haskell, if you need a type to be constrained (e.g. to support `<`) you declare that explicitly with the `Ord` typeclass constraint. With duck typing, the constraint is implicit (a run-time error because the operation isn't there). C++ does the can-it-quack check at compile time through the SFINAE error - the template won't instantiate for types that can't support the operations you used, so the constraint is static but implicit. –  Nov 18 '13 at 06:24
  • @Tony D - the reason "ad-hoc" and "constrained" tend to be considered synonyms in Haskell is because they're implemented via (different aspects of) the same language feature - typeclasses. Without that feature, all you have is unconstrained parametric polymorphism, so those are treated as synonyms too. Strictly, in Haskell, the parametric polymorphism is constrained by specifying a typeclass constraint, and the methods of the typeclass are (separately) ad-hoc because each type defines its own instance with it's own implementations. –  Nov 18 '13 at 06:36
  • @Tony D - If that precise formal meaning was what people really meant there'd be no problem, but they usually don't. Anyway, with "constrained polymorphism" and "ad-hoc polymorphism" often treated as synonyms, I think it's important to notice that the parametric polymorphism in templates (and duck-typing) is implicitly constrained - you haven't declared that your type must support quacking, but it can't quack like a duck if it can't quack. In Haskell, the constraint is explicit. In C++, via SFINAE, it's an implicit static constraint. In Python it's a run-time error, but still a constraint. –  Nov 18 '13 at 06:47
  • @Tony D - additionally in C++, you have template specialization - you can provide an ad-hoc alternative implementation of your template that overrides it in particular cases. This is one reason for the "Is Not An Error" aspect of SFINAE - another template may provide a correct alternative anyway. This is ad-hoc polymorphism because there are multiple implementations - multiple templates. It's constrained because each template has constraints - some implicit (only types with these operations, only cases where a more specialized template doesn't exist) and some explicit. –  Nov 18 '13 at 06:51
  • @Tony D - I should probably delete all these comments (and my answer) and write a blog post. –  Nov 18 '13 at 06:56
  • @Steve314: "C++ does the can-it-quack check at compile time through the SFINAE error - the template won't instantiate" - a template that doesn't use SFINAE yields a compile time error at `x.quack()`, while SFINAE allows a compile-time test and falling through to another function that avoids an error by not even trying to quack. If a more explicit fatal check is desired, sans Concepts we have library offerings e.g. http://www.boost.org/doc/libs/1_54_0/libs/concept_check/concept_check.htm . But these just shift around how/where the compile-time error's reported for parametric polymorphism. – Tony Delroy Nov 18 '13 at 07:01
  • @Steve314: oh - didn't realise you had posted an answer - I'll have a read (if it's still there!) ;-) – Tony Delroy Nov 18 '13 at 07:02
  • @Tony D - Sorry - "through the SFINAE error" was a brainfart, I meant "through the SFINAE rule". I'm not sure if we're disagreeing or not - to clarify what I was saying, when you call `T::quack ()` in your template, that implicitly constrains that template to being instantiated for only types `T` that have `quack ()`. The `quack` isn't an error even if you instantiate for some type `T` that doesn't have it, but if there's no other template that covers that case, that *is* an error. So it's not a general template with an error in some cases, it's a template with an implicit constraint. –  Nov 18 '13 at 07:18
  • @Steve314 I suspect we're talking around the same knowledge, just unable to communicate unambiguously enough to know we're agreeing ;-). Tricky! SFINAE tends to be very explicit in at least one potential match: you must test *in function parameters or return type* (even if you really only want to use a property in the function body), and in practice it's often done with `std::enable_if` or similar for convenience and to emphasise the nature of the SFINAE function-matching criteria. Not all candidates will be cluttered with explicitly SFINAE-related code though. – Tony Delroy Nov 18 '13 at 08:54
  • @TonyD: really nice answer, but my question is how & why strlen() function is slower? – Destructor Jul 30 '15 at 14:32
  • @PravasiMeet: each `std::string` object tracks its own `.size()`, so you can simply read the member variable at any time, but `strlen(const char*)` has to scan along and count each character in the text until it finds an ASCII NUL terminator - the longer the text, the longer `strlen()` takes. Most compilers will evaluate `strlen()` of string literals (e.g. `strlen("abc")`) at compile time though - so at least in that limited case it's insignificant....) – Tony Delroy Jul 31 '15 at 03:31
15

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...

  • First, there are monomorphic functions. The implementation of the function is uniquely identified by the function name. None of the parameters is special.
  • Then, there is single dispatch. One of the parameters is considered special, and used (along with the name) to identify which implementation to use. In OOP, we tend to think of this parameter as "the object", list it before the function name etc.
  • Then, there is multiple dispatch. Any/all parameters contribute to identifying which implementation to use. Therefore, once again, none of the parameters needs to be special.

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++.

  • +1 Lots of interesting points and insights. I've only spent a few hours reading about Haskell so "`a` here is an unconstrained polymorphic type [...] so there's not much we can do with values of that type." was of interest - in C++ sans Concepts you're not restricted to only attempting a specific set of operations on an argument of a type specified as a template parameter... libraries like boost concepts work the other way - making sure the type supports operations you specify, rather than guarding against accidental use of additional operations. – Tony Delroy Nov 18 '13 at 07:24
  • @Tony - Concepts are a way to explicitly constrain the polymorphism of templates. The implicit constraints obviously won't go away because of compatibility, but explicit constraints will definitely improve things significantly. I'm pretty sure some past plans for concepts were somewhat related to Haskell typeclasses, though I didn't look into them that deeply and when I last looked "shallowly" I didn't know much Haskell. –  Nov 18 '13 at 07:57
  • "The implicit constraints obviously won't go away because of compatibility" - from memory, C++0x Concepts did (promise to :-/) prevent "implicit constraints" - you could only use the type in ways promised by the Concepts. – Tony Delroy Nov 18 '13 at 08:56
2

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;
            }
        }
    }
};
Joe
  • 1,059
  • 2
  • 11
  • 21
2

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

Eric Z
  • 14,327
  • 7
  • 45
  • 69
  • 1
    Unfortunately, although correct, this is misleading. Template functions can get implicit constraints because of the SFINAE rule - using an operation within the template implicitly constrains the polymorphism - and template specialization can provide ad-hoc alternative templates that override the more general templates. So a template (by default) provides unconstrained parametric polymorphism, but there's no enforcement of that - there's at least two ways it can become constrained or ad-hoc. –  Nov 17 '13 at 08:58
  • In fact your example - sorting - implies a constraint. Sorting only works for types that are ordered (ie provide the `<` and similar operators). In Haskell, you'd express that requirement explicitly using the class `Ord`. The fact that you get a different `<` depending on the particular type (as supplied by the instance of `Ord`) would be considered ad-hoc polymorphism. –  Nov 17 '13 at 09:03
1

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);
}
user2976089
  • 347
  • 1
  • 5
  • 14
0

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)

-3

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.

Sanchit
  • 1,145
  • 11
  • 6
  • As I mentioned for better understanding of the Polymorphism in c++ I used above example. This might help a fresher to actually understand and relate what is the meaning or what's happening behind the code while performing at interview. Thank you! – Sanchit Nov 16 '14 at 10:32
  • op asked "polymorphism in c++". your answer is way too abstract. – StahlRat Sep 03 '16 at 16:43