1.37k likes | 1.5k Views
C++ Training Datascope Lawrence D’Antonio. Lecture 7 An Overview of C++: What is Polymorphism? – Parametric Polymorphism. What is polymorphism?. Parametric. Universal. Subtype. Polymorphism. Overloading. Ad-hoc. Coercion.
E N D
C++ TrainingDatascopeLawrence D’Antonio Lecture 7 An Overview of C++: What is Polymorphism? – Parametric Polymorphism
What is polymorphism? Parametric Universal Subtype Polymorphism Overloading Ad-hoc Coercion Different types of objects respond to the same message and use the appropriate method.
Parametric Polymorphism • Parametric polymorphism parametrizes the object type (e.g., a list class, where the type of object stored is parametrized). • Parametric polymorphism in C++ is implemented as templates. • Both classes and functions may be templates.
Template Functions template<class T> T max(T a, T b) { return a > b ? a : b; }
Is this legal? int a,b = 6; const int c = 3; double x,y = 3.2; a = max(b,4); a = max(b,c); x = max(y,3.3); x = max(b,y);
a = max(b,4); //Legal, max<int,int> a = max(b,c); //Legal, max<int,int> x = max(y,3.3); //Legal, max<double,double> x = max(b,y); //Illegal, no max<int,double> A template function is called only when there is an exact match for type parameters (only trivial conversions, such as const int to int are allowed). But the following is legal. x = max<double>(4,4.2);
Better max? template<class S, class T> T max(S a, T b) { return a > b ? a : b; } main() { int a, b = 3; double x, y = 3.2; a = max(b,5); x = max(y,5.4); x = max(b,y); x = max(y,b); return 0; }
a = max(b,5); //Legal, returns 5 x = max(y,5.4); //Legal, returns 5.4 x = max(b,y); //Legal, 3.2 x = max(y,b); //Legal, but //returns 3.0!
Best max? template<class R, class S, class T> R max(S a, T b) { return a > b ? a : b; } main() { int a, b = 3; double x, y = 3.2; a = max(b,5); x = max(y,5.4); x = max(b,y); x = max(y,b); return 0; }
Doesn’t compile. The function max() is supposed to have 3 template parameters. But each call only uses 2 parameters.
Try this max template<class R, class S, class T> R max(S a, T b) { return a > b ? a : b; } main() { int a, b = 3; double x, y = 3.2; a = max<int>(b,5); x = max<double>(y,5.4); x = max<double>(b,y); x = max<double>(y,b); return 0; }
Is this legal? • Return back to the original definition of max. int x = 5, y = 6; int *p = &x, *q = &y; int z = max(p,q);
Legal, but probably not what you want. max(p,q) compares addresses, not data values.
Can we fix this? template<class T> T max(T a, T b) { return a > b ? a : b; } template<class T> T* max<T *a, T *b> { return *a > *b ? a : b; }
Should we fix this? • Probably not. Overloading the max function to compare dereferenced pointers means that we can’t compare addresses using max.
STL version of max template<class T> const T &max(const T &a, const T &b) { return a < b ? b : a; }
Another problem const char *s = “Hello”; const char *t = “World”; std::string s = max(s,t); std::cout << s; What does this print out? Who knows? max returns the char* with the larger memory address.
A Solution • Overload the max function. The second is called a template specialization. template<class T> const T& max(const T&a, const T&b) { return a < b ? b : a; } const char* max(const char *a,const char* b) { return std::strcmp(a,b) < 0 ? b : a; }
STL Solution template<class T> const T& max(const T&a, const T&b) { return a < b ? b : a; } template<class T, class BinPred> const T& max(const T&a, const T&b, BinPred comp) { return comp(a,b) ? b : a; }
Using a predicate bool comp(const char *a, const char *b) { return std::strcmp(a,b) < 0; } const char *s = “Hello”; const char *t = “World”; std::string s = max(s,t,comp);
Functor solution class Comp { public: bool operator()(const char *a, const char *b) { return std::strcmp(a,b) < 0; } }; const char *s = “Hello”; const char *t = “World”; std::string s = max(s,t,Comp());
Is this legal? std::string s1(“apple”); std::string s2(“tomato”); std::max(“apple”,”peach”); std::max(“apple”,”tomato”); std::max(“apple”,s1); std::max(s1,s2);
std::max(“apple”,”peach”); //Legal, both arguments are const char[5] std::max(“apple”,”tomato”); //Illegal, arguments are different types std::max(“apple”,s1); //Illegal, arguments are different types std::max(s1,s2); //Legal, both arguments are type string
Is this legal? template<class T> T foo() { return T(); } template<class T> void bar() { T t; } main() { int x = foo(); bar(); return 0; }
Not legal for two reasons. int x = foo(); Illegal because the compiler doesn’t know which version of foo() to call. It won’t determine that foo() should return an int by looking at the LHS. bar(); Illegal because the compiler doesn’t know which version of bar() to call.
Legal version of example template<class T> T foo() { return T(); } template<class T> void bar() { T t; } main() { int x = foo<int>(); bar<int>(); return 0; }
Template name lookup • Template name resolution involves what is known as “two-phase name lookup”. • Template names are divided into two categories: dependent and non-dependent names. • Dependent and non-dependent names are resolved at different times.
Dependent names • Dependent names have definitions that depend on template parameters, but have no declaration within the template definition. • Dependent names are only resolved at the time of instantiation.
Non-dependent names • Non-dependent names are names that don’t depend on a template parameter, the name of the template itself, and names declared within it (members, friends, and local variables).
Example template<class T> class X { T t; public: X(const T &a):t(a) { t.init(); } template<class U> T foo() { U u = t.begin(); return *u; } };
Name resolution in example • Non-dependent names X, t, a, X(const T &), foo(), U, u • Dependent names T::init(), T::begin()
SFINAE • “Substitution failure is not an error.” • When examining which template function to call from a set of overloaded template functions, there is no error if some substitutions are illegal.
Example template<class Func, class T> void apply(Func f, T x) { f(x); } template <class T> void multi(T) { } template <class T*> void multi(T *) { } main() { apply(multi<int>,5); return 0; }
apply(multi<int>,5) calls multi(T) with int substituting for T. The failure of the substitution of int in multi(T*) does cause an error.
Is this legal? template<int N> int g() { return N; } template<int *P> int g() { return *P; } main() { return g<1>(); }
Yes, this is legal (if odd looking). g<1> binds to g<int>, the failure to bind to g<int *> is not an error.
Is this legal? template <class Alloc> class container_helper { typedef Alloc::value_type value_type; };
Not legal, the compiler has no idea what the dependent name Alloc::value_type represents. Here is the correct version. template <class Alloc> class container_helper { typedef typename Alloc::value_type value_type; };
Is this legal? template <class Alloc> class container_helper { typedef typename Alloc::value_type value_type; typedef std::pair<value_type,value_type> element_type; };
Yes, this is legal. typedef std::pair<value_type,value_type> element_type; This can be resolved in scope.
Is this legal? template <class Alloc> class container_helper { typedef typename Alloc::value_type value_type; typedef std::pair<value_type,value_type> element_type; typedef typename Alloc::rebind<element_type>::other element_allocator; };
Not legal, perhaps surprisingly. The compiler cannot determine from Alloc::rebind<element_type>::other what rebind refers to (is it an object, a function, or Superman?).
ADL • Argument-dependent lookup applies to unqualified names where it appears that a nonmember function is being called. • ADL proceeds by looking up a name in namespaces and classes associated with the types of the function call arguments.
Which functions are called? #include <iostream> namespace X { template <class T> void f(T) { std::cout << "f<T>\n"; } } namespace N { using namespace X; enum E{ e1 }; void f(E) { std::cout << "f(E)\n"; } } void f(int) { std::cout << "f(int)\n"; } main() { ::f(N::e1); f(N::e1); return 0; }
::f(N::e1); //Qualified name, so calls global f, no ADL used f(N::e1); //Calls N::f. The call argument N::e1 is associated //with namespace N. So this means that N::f is //preferred over ::f. Note that X::f is not considered, //because using directives are ignored in ADL.
Is this legal? template<typename T, typename Alloc = std::allocator<T> > class my_container : private container_helper<Alloc>::element_allocator { //... };
Perhaps surprisingly this is legal. The dependent name container_helper<Alloc>::element_allocator cannot be resolved,. But since it is being used as a base class, the compiler is happy.
What is rebind? • A template typedef. template <class T> class allocator { . . . template <class U> struct rebind { typedef allocator<U> other; }; ... template <class U> allocator(const allocator<U>&); };
Use of rebind template <class T, class Allocator = allocator<T> > class list { private: typedef . . . listnode; typedef typename Allocator::rebind<listnode>::other Node_allocator; Node_allocator alloc_; list_node* make_node() { return new(alloc_.allocate(1)) list_node; } public: list(const Allocator& a = Allocator()) : alloc_(a) { } // implicit conversion . . . };
Template classes • The declaration and definition of a template class “must” be in the same header file.