Is there real static polymorhism in C++?
Absolutely - there are three mechanisms for static polymorphism: templates, macros and function overloading.
So as I understood, the C++ compiler will divide a single defined function into various number (depends on calls count with different type) of functions. Am I right or not?
That's the general idea. The number of functions that get instantiated depends on the number of permutations of template parameters, which may be explicitly specified as in function<int>
and function<double>
or - for templates that use the template parameters to match function arguments - automatically derived from the function arguments, for example:
template <typename T, size_t N>
void f(T (&array)[N])
{ }
double d[2];
f(d); // instantiates/uses f<double, 2>()
You should end up with a single copy of each instantiated template in the executable binary image.
I want to know if I can describe the code above as static polymorphism?
Not really.
template<> function
is instantiated for two types
crucially, polymorphism is not used to choose which of the two instantiations of function
to dispatch to at the call sites
trivially, during such instantiations typeid(T)
is evaluated for int
and double
and effectively behaves polymorphically from a programmer perspective (it's a compiler keyword though - implementation unknown)
trivially, a mix of static and nominally dynamic (but here likely optimisable to static) polymorphism supports your use of std::cout
Background - polymorphism and code generation
The requirement I consider crucial for polymorphism is:
But, what we're trying to achieve with polymorphism is a bit more general:
reuse of algorithmic code for multiple types of data without embedding explicit type-detection and branching code
- that is, without the program source code containing
if (type == X) f1(x) else f2(x);
-style code
reduced maintenance burden as after explicitly changing a variable's type fewer consequent changes need to be manually made throughout the source code
These bigger-picture aspects are supported in C++ as follows:
instantiation of the same source code to generate distinct behaviours (machine code) for some other type or permutation of types (this is an aspect of parametric polymorphism),
- actually known as "instantiation" for templates and "substitution" for preprocessor macros, but I'll use "instantiation" hereafter for convenience; conceptually, re-compilation or re-interpretation...
implicit dispatch (static or dynamic) to distinct behaviour (machine code) appropriate to the distinct type(s) of data being processed.
...and in some minor ways per my answer at Polymorphism in c++
Different types of polymorphism involve either or both of these:
dispatch (2) can happen during instantiation (1) for templates and preprocessor macros,
instantiation (1) normally happens during dispatch (2) for templates (with no matching full specialisation) and function-like macros (kind of cyclic, though macros don't expand recursively)
dispatch (2) can be happen without instantiation (1) when the compiler selects a pre-existing function overload or template specialisation, or when the compiler triggers virtual/dynamic dispatch.
What does your code actually use?
function<int>
and function<double>
reuse the function
template code to create distinct code for each of those types, so you are getting instantiation (1) as above. But, you are hard-coding which instantiation to call rather than having the compiler implicitly select an instantiation based on the type of some parameter, i.e. so you don't directly utilise implicit dispatch ala (2) when calling function
. Indeed, function
lacks a parameter that the compiler could use for implicit selection of a template instantiation.
Instantiation (1) alone is not enough to consider your code to have used polymorphism. Still, you've achieved convenient code re-use.
So what would be unambiguously polymorphic?
To illustrate how templates can support dispatch (2) as well as instantiation (1) and unarguably provide "polymorphism", consider:
template<typename T>
void function(T t)
{
std::cout << typeid(T).name() << std::endl;
}
function(4); // note: int argument, use function<int>(...)
function(12.3); // note: double argument, use function<double>(...)
The above code also utilises the implicit dispatch to type-appropriate code - aspect "2." above - of polymorphism.
Non type parameters
Interestingly, C++ provides the ability to instantiate templates with integral parameters such as boolean, int
and pointer constants, and use them for all manner of things without varying your data types, and therefore without any polymorphism involved. Macros are even more flexible.
Note that using a template in a C.R.T.P. style is NOT a requirement for static polymorphism - it's an example application thereof. During instantiation, the compiler exhibits static polymorphism when matching operations to implementations in the parameter-specified type.
Discussion on terminology
Getting a definitive definition of polymorphism is difficult. wikipedia quotes Bjarne Stroustrup's online Glossary "providing a single interface to entities of different types": this implies struct X { void f(); }; struct Y { void f(); };
already manifests polymorphism, but IMHO we only get polymorphism when we use the correspondence of interface from client code, e.g. template <typename T> void poly(T& t) { t.f(); }
requires static polymorphic dispatch to t.f()
for each instantiation.