3370-Templates

advertisement
Understanding C++ Templates
CNS 3370
Copyright 2006, Fresh Sources, Inc.
Agenda
•
•
•
•
•
•
Executive Overview
Template Parameters
Function Template Issues
Template Specialization
Member Templates
Template Idioms
Executive Overview
•
•
•
•
Generic Programming
Compile-time Code Generation
Implicit Interfaces
Template Terminology
Generic Programming
• Writing type-transparent code
– Not a new idea
– Object mega-hierarchy; void* in C
• C++ prefers type-safe generics
– Based on innovations from ML and Ada
– Statically-checked
– Allows efficient use of built-in types
Compile-time Code Generation
• Separate code versions are
automatically generated
• On-demand code
generation
– Implicit and explicit
• Compile-time programming
– Selective code generation
• Potential for ODR Violations
– Smart linkers solve that
• Potential for Code Bloat
template<typename T>
class Stack {
T* data;
size_t count;
public:
void push(const T& t);
// etc.
};
// Explicit instantiation
Stack<int> s;
What Gets Generated?
• If using a function template, the requested function
is instantiated
• If using a class template, the requested version of
the class definition is instantiated
– For the compiler’s use (just like regular classes)
– But only the member functions actually used are
generated
• Usually :-)
Instantiation On Demand
class X {
public:
void f() {}
};
class Y {
public:
void g() {}
};
template<typename T>
class Z {
T t;
public:
void a() { t.f(); }
void b() { t.g(); }
};
int main() {
Z<X> zx;
zx.a(); // Z<X>::b() not attempted
Z<Y> zy;
zy.b(); // Z<Y>::a() not attempted
}
Question: When are the names f and
g looked up by the compiler?
Where Should Template Code
Reside?
• Totally in header files
• The template code must be available during
instantiation
– This is the Inclusion Model of template compilation
Implicit Interfaces
• aka “Duck Typing”
– If it quacks like a duck, …
(fits the bill :-)
• The status quo for
dynamically-typed
languages
– Perl, Python, PHP, Ruby…
• C++ statically verifies that
generic types support
required operations
template<class T>
const T& min(const T& a,
const T& b)
{
return (a < b) ? a : b;
}
…
min(i, j) // T deduced
Template Terminology
A First Look
• Template Parameter
– Names inside <>’s after the template keyword (<T>)
• Template Argument
– Names inside <>’s in a specialization (<int>)
• Template Specialization
– What results when you give a template some arguments (a class,
function, or another template) (Stack<int>)
– A specialization may or may not be an instantiation
• Instantiation
– The class or function generated for a given complete set of
template arguments
– An instantiation is always a specialization
Template Executive Summary
• Templates are instructions for compile-time code
generation
• They enable type-safe, type-transparent code
• Template parameters constitute implicit interfaces
– “Duck Typing”
– “Compile-time polymorphism”
• Classic OOP uses explicit interfaces and runtime
polymorphism
– Templates and OOP complement each other
Sample Programs
Template version of Programs
2 (Pool) and 3 (Bits)
Template Parameters
• 3 Kinds…
• Type parameters
– the most common (vector<int>)
• Non-type
– compile-time integral values (bitset<10>, for example)
• Templates
– “template template parameters”
Type Parameters
• The original motivation for templates
– “Generic Programming”
• Container logic is independent of its containee’s type
• Containers of “T”:
– template<class T>
// T is a type parm
class vector {
T* data;
…
};
– vector<int> v; // int is a type argument
Non-type Template Parameters
• Must be compile-time constant values
– usually integral expressions
– can also be addresses of static objects or functions
– should probably be called “value parameters”
• Often used to place member arrays on the runtime
stack
– like with bitset<128> b;
– see next slide
• Often used in Template Metaprogramming
– compile-time expressions governing code generation
Using std::bitset
• <need sample code here>
Bitset Implementation
(from STLPort)
template<size_t _Nw>
struct _Base_bitset {
typedef unsigned long _WordT;
_WordT _M_w[_Nw]; // Can go on stack
. . .
};
template<size_t _Nb>
class bitset : public
_Base_bitset<__BITSET_WORDS(_Nb) >
{ . . . };
Default Template Arguments
• Like default function arguments
– if missing, the defaults are supplied
– only allowed in class templates
• template<class T = int, size_t N = 100>
class FixedStack {
T data[N];
…
};
FixedStack<> s1;
// = <int, 100>
FixedStack<float> s2; // = <float,100>
The vector Container Declaration
• template<class T,
class Allocator = allocator<T> >
class vector;
• Note how the second parameter uses the first
• Note the space between the ‘>’s
– Will be fixed in C++0x
Template Template Parameters
• Templates are not types!
– They are instructions for generating types
– “Meta-types”, if you will
• If you plan on using a template parameter itself as
a template, the compiler needs to know
– otherwise it won’t let you do template things with it
– You usually don’t use a name for its parameters
• Examples follow
A Simple Expandable Sequence
(will be used as a template argument)
// A simple, expandable sequence (like vector)
template<class T>
class Array {
enum { INIT = 10 };
T* data;
size_t capacity;
size_t count;
public:
Array() {
count = 0;
data = new T[capacity = INIT];
}
~Array() { delete [] data; }
void push_back(const T& t) {...}
void pop_back() {...}
T* begin() { return data; }
T* end() { return data + count; }
};
Passing Array as a template argument
template<class T, template<class> class Seq>
class Container {
Seq<T> seq;
public:
void append(const T& t) { seq.push_back(t); }
T* begin() { return seq.begin(); }
T* end() { return seq.end(); }
};
int main() {
Container<int, Array> container; // Pass template
container.append(1);
container.append(2);
int* p = container.begin();
while(p != container.end())
cout << *p++ << endl;
}
Function Template Issues
• Type Deduction of Arguments
• Function template overloading
• Partial Ordering of Function Templates
Type Deduction in Function
Templates
• Under most circumstances, the compiler deduces type
parameters from the arguments in the call
– the corresponding specialization is instantiated automatically
• You can use a fully-qualified call syntax if you want to:
int x = min<int>(a, b); // vs. min(a, b);
• Sometimes you have to:
– when the arguments are different types ( min(1.0, 2) )
– when the template argument is a return type, and therefore
cannot be deduced by the arguments
– Example on next two slides
String Conversion Function
Templates
// StringConv.h
#include <string>
#include <sstream>
template<class T> T fromString(const std::string& s) {
std::istringstream is(s);
T t;
is >> t;
return t;
}
template<class T> std::string toString(const T& t) {
std::ostringstream s;
s << t;
return s.str();
}
#include <complex>
#include <iostream>
#include "StringConv.h"
using namespace std;
int main() {
// Implicit Type Deduction
int i = 1234;
cout << "i == \"" << toString(i) << "\"" << endl;
float x = 567.89;
cout << "x == \"" << toString(x) << "\"" << endl;
complex<float> c(1.0, 2.0);
cout << "c == \"" << toString(c) << "\"" << endl;
cout << endl;
// Explicit Function Template Specialization
i = fromString<int>(string("1234"));
cout << "i == " << i << endl;
x = fromString<float>(string("567.89"));
cout << "x == " << x << endl;
c = fromString<complex<float> >(string("(1.0,2.0)"));
cout << "c == " << c << endl;
}
Another Example
implicit_cast
template <class U, class T>
U implicit_cast(const T& t) {
return t;
}
• Makes a legal implicit conversion visible in code
• Must specify return type (U)
– Can’t be deduced (s = implicit_cast<string>(x);)
• Can omit T
– But only because we put it last in the parameter list
– Couldn’t say implicit_cast<?, string>
Function Template Overloading
• You can define multiple functions and function
templates with the same name
• The “best match” will be used
– A regular function is preferred over a template
• All other things being equal
– Can force using the template with “<>”
• You can also overload a function template by
having a different number of template parameters
template<class T>
const T& min(const T& a, const T& b) {
return (a < b) ? a : b;
}
const char* min(const char* a, const char* b) {
return (strcmp(a, b) < 0) ? a : b;
}
double min(double x, double y) {
return (x < y) ? x : y;
}
int main() {
const char *s2 = "say \"Ni-!\"", *s1 = "knights who";
cout << min(1, 2) << endl;
// 1: 1 (template)
cout << min(1.0, 2.0) << endl; // 2: 1 (double)
cout << min(1, 2.0) << endl;
// 3: 1 (double)
cout << min(s1, s2) << endl;
// 4: "knights who"
//
(const char*)
cout << min<>(s1, s2) << endl; // 5: say "Ni-!"
//
(template)
}
Partial Ordering of Function
Templates
template<class T> void f(T) {
cout << "T" << endl;
}
template<class T> void f(T*) {
cout << "T*" << endl;
}
template<class T> void f(const T*) {
cout << "const T*" << endl;
}
int main() {
f(0);
// T
int i = 0;
f(&i);
// T*
const int j = 0;
f(&j);
// const T*
}
Things to Remember…
About Function Templates
• Arguments that can be deduced will be
– The rest you must provide
– Put those first in the argument list so the others can be
left to deduction
• Standard Conversions do not apply when using
unqualified calls to function templates
– Arguments must match parameters exactly
– Except const/volatile adornments are okay
• Function Templates can be overloaded
– The “best match” is used
Template Specialization
• A template by nature is a generalization
• It becomes specialized for a particular use when
the actual template arguments are provided
• A particular instantiation is therefore a
specialization
• But there is another type of “specialization”…
Explicit Specialization
• What if you want special “one-off” behavior for
certain combinations of template arguments?
• You can provide custom code for such cases
– both full or partial specializations for class templates
• Meaning all or some of the template arguments are
specified
– the compiler will use your explicit versions instead of
what the “primary template” would have instantiated
– You are writing the “instantiation”
• Full specialization uses the template<> syntax
A Full Class Specialization
Example
// A Primary Template
template<class T>
class Foo {
public:
void f();
void g();
};
template<class T>
void Foo<T>::f() {
cout << "f using primary template\n";
}
template<class T>
void Foo<T>::g() {
cout << "g using primary template\n";
}
Full Class Specialization
Example
// The Full Specialization
template<>
class Foo<int> {
public:
void f();
void g();
};
// NOTE: No template keyword here
// Also: These go in a .cpp
void Foo<int>::f() {
cout << "f using int specialization\n";
}
void Foo<int>::g() {
cout << "g using int specialization\n";
}
Full Class Specialization
Example
int main() {
Foo<char> c;
Foo<int> i;
c.f();
c.g();
i.f();
i.g();
}
/* Output:
f using primary template
g using primary template
f using int specialization
g using int specialization
*/
Specializing Selected Member
Functions
• You can do a full specialization of only selected
member functions
• The other member functions come from the
primary template
• (See next slide)
• Other members can be thus specialized
– Statics, Member templates
Selective Member Function Spec.
Example
// Replaces class full specialization:
template<>
void Foo<int>::g() {
cout << "g using int specialization\n";
}
int main() {
Foo<char> c;
Foo<int> i;
c.f();
c.g();
i.f();
i.g();
}
/* Output
f using primary template
g using primary template
f using primary template
g using int specialization
*/
Partial Specialization of Class
Templates
• Can specialize on a subset of template arguments
– leaving the rest unspecified
– can also specialize on:
• “pointer-ness”
• “const-ness”
• equality
• vector<bool> is actually a partial specialization
– It specializes the data type, but leaves the allocator type “open”:
template<class Allocator>
class vector<bool, Allocator> {…};
• The “most specialized, most restricted” match is preferred
• See next slide
template<class T, class U> class C {
public:
void f() { cout << "Primary Template\n"; }
};
template<class U> class C<int, U> {
public:
void f() { cout << "T == int\n"; }
};
template<class T> class C<T, double> {
public:
void f() { cout << "U == double\n"; }
};
template<class T, class U> class C<T*, U> {
public:
void f() { cout << "T* used\n"; }
};
template<class T, class U> class C<T, U*> {
public:
void f() { cout << "U* used\n"; }
};
template<class T, class U> class C<T*, U*> {
public:
void f() { cout << "T* and U* used\n"; }
};
template<class T> class C<T, T> {
public:
void f() { cout << "T == U\n"; }
};
int main() {
C<float, int>().f();
// 1: Primary template
C<int, float>().f();
// 2: T == int
C<float, double>().f();
// 3: U == double
C<float, float>().f();
// 4: T == U
C<float*, float>().f();
// 5: T* used [T is float]
C<float, float*>().f();
// 6: U* used [U is float]
C<float*, int*>().f();
// 7: T* and U* used [float, int]
// The following are ambiguous:
// 8: C<int, int>().f();
// 9: C<double, double>().f();
// 10: C<float*, float*>().f();
// 11: C<int, int*>().f();
// 12: C<int*, int*>().f();
}
Class Specialization Summary
• You must define all functions you want clients to
have when specializing a class
– An alternative is to only specialize selected member
functions, leaving the rest to the primary template
• You can do full or partial specializations of class
templates
– Don’t specialize function templates
• Except as described above for member functions
• Overloading and specialization of function templates don’t
mix well
More Points to Ponder
About Specialization
• Template parameters in a specialization don’t have
to match the primary template
– A total specialization is not a template
– A partial specialization is a template
• But the specialization part (in brackets after the
class template name) must match:
– template<class T> Stack<T*> {…};
// 1 type parm
– template<> Stack<char*> {…};
// 1 type parm
– template<class R, class A> Stack<R (*)(A)> {…}; // 1 type parm
Question
• What kind of things can you define as members of
a class?
Answer
• Variables
– Data members; either static or non-static
• Functions
– Member functions, either static and non-static
• Types
– Either nested classes or typedefs
• Templates
– “Member Templates”
– This is where need for a special use of the template
keyword arises
Member Types
Nested Classes
class bitstring {
class bitproxy{
…
};
bitproxy operator[](int pos) {…}
};
If public, could say:
bitstring::bitproxy…
Member Types
Nested typedefs
template<class T,…>
class vector {
public:
class MyIteratorType {…};
typedef MyIteratorType iterator;
…
};
Can say:
vector<int>::iterator p = v.begin();
The typename keyword
• Can use typename instead of class in a template
parameter declaration
– Some people use class when the expected
argument(s) must not be built-in types
– And they use typename when anything goes
• Sometimes, though, you need to help the compiler
know that an identifier represents a type
• In particular, when you want to use a member type
from a template parameter
The typename keyword
~ continued ~
• Consider the expression T::X
– When inside a template with type parameter T
• The compiler assumes a name qualified by a
template type parameter refers to a static member
(the parser has to assume something, since T is
unknown at first)
• X is a dependent name
• If X is a type, use the typename keyword to clue
the compiler
// A Print function for standard sequences
template<typename T,
template<typename U,
typename = allocator<U> >
class Seq>
void printSeq(Seq<T>& seq) {
for(typename Seq<T>::iterator b = seq.begin();
b != seq.end();)
cout << *b++ << endl;
}
int main() {
// Process a vector
vector<int> v;
v.push_back(1);
v.push_back(2);
printSeq(v);
// Process a list
list<int> lst;
lst.push_back(3);
lst.push_back(4);
printSeq(lst);
}
iterator_traits
An application of partial specialization
• A technique for endowing naked pointers with
what other iterators have:
–
–
–
–
–
difference_type
value_type
pointer
reference
iterator_category
Implementing iterator_traits
// Primary template
template<class Iterator> struct iterator_traits {
typedef typename Iterator::difference_type difference_type;
typedef typename Iterator::value_type value_type;
typedef typename Iterator::pointer pointer;
typedef typename Iterator::reference reference;
typedef typename Iterator::iterator_category
iterator_category;
};
// Partial specialization for pointer types
template<class T> struct iterator_traits<T*> {
typedef ptrdiff_t difference_type;
typedef T value_type;
typedef T* pointer;
typedef T& reference;
typedef random_access_iterator_tag iterator_category;
};
Using iterator_traits
// Print any standard sequence or an array
template<class Iter>
void print(Iter b, Iter e) {
typedef typename iterator_traits<Iter>::value_type T;
copy(b, e, ostream_iterator<T>(cout, "\n"));
}
Member Templates
• Can also nest template definitions inside a class
• Inside of a class template, too
• Very handy for conversion constructors:
– template<typename T> class complex {
public:
template<class X> complex(const complex<X>&);
– Inside of STL sequences:
template <class InputIterator>
deque(InputIterator first, InputIterator last,
const Allocator& = Allocator());
• Example on next slide
A Member Class Template
template<class T> class Outer {
public:
template<class R> class Inner {
public:
void f();
};
};
template<class T> template<class R>
void Outer<T>::Inner<R>::f() {
cout << "Outer == " << typeid(T).name() << endl;
cout << "Inner == " << typeid(R).name() << endl;
cout << "Full Inner == " << typeid(*this).name()
<< endl;
}
int main() {
Outer<int>::Inner<bool> inner;
inner.f();
}
Output from Previous Example
Outer == int
Inner == bool
Full Inner == Outer<int>::Inner<bool>
Member Function Templates
• Used in practice more than member class
templates
• Cannot be virtual
– vtables are fixed-size (traditional compiler techniques)
– Since an arbitrary number of instantiations can occur
per program, a fixed vtable won’t do
– If you want to solve that problem, be my guest :-)
A Generic Programming
Session
• Task: Write a class that simulates the composition
of an arbitrary number of functions
– h(x) = f(g(x))
– comp(x) f1(f2(…fn(x)…))
• Strategy:
– Hold the function pointers in some sequence container
– Apply them in reverse order, starting with fn(x)
March 2008
Copyright © 2008, Fresh Sources,
Inc.
58
From Specialization to
Generalization
•
•
•
•
compose1.cpp (function of double)
compose2.cpp (functions of T)
compose3.cpp (generalizes sequence type)
compose4.cpp (generalizes the callable type)
– Using a C++0x feature: function
• compose5.cpp (deduces T)
– Using function::result_type and iterator_traits
• compose6.cpp (uses std::accumulate)
• compose7.cpp (deduces iterator type)
Name Classification
• Names can be nicely dichotomized as:
– Unqualified
• x, f( )
– Qualified (provides scope for lookup context)
• A::x, x.f( ), p->f( )
• Names inside templates are also either:
– Dependent
• They depend on a template parameter: e.g., T::iterator
• We used typename earlier for dependent types
– Non-dependent
Template Name Lookup Issues
• Dependent names cannot be resolved when the
compiler first encounters a template definition
• The compiler must wait until instantiation time to
resolve those issues
– When actual template arguments are known
• Hence, a 2-step template compilation process:
– 1) Template Definition
– 2) Template Instantiation
Two-phase Template Compilation
• Template definition time
– The template code is parsed
– Obvious syntax errors are caught
– Non-dependent names are looked up in the context of
the template definition (no waiting needed)
• Template instantiation time
– Dependent names are resolved
– Which specialization to use is determined
• A key motivation for 2-phase lookup
• Examples follow
What Output Should Display?
void f(double) { cout << "f(double)" << endl; }
template<class T> class X {
public:
void g() { f(1); }
};
void f(int) { cout << "f(int)" << endl; }
int main() {
X<int>().g();
}
Answer
• f( ) is a non-dependent name, so it is looked up
when the template code is parsed
• f(double) is in scope at the time, so the call
resolves to that function, not f(int)
• Some compilers do the wrong thing here
– For historical reasons (the rules were decided on late in
the game)
A Second Example
The underlined calls fail!
template<class T, class Compare>
class PQV : public vector<T> {
Compare comp;
public:
PQV(Compare cmp = Compare()) : comp(cmp) {
make_heap(begin(), end(), comp);
}
const T& top() const { return front(); }
void push(const T& x) {
push_back(x);
push_heap(begin(), end(), comp);
}
void pop() {
pop_heap(begin(), end(), comp);
pop_back();
}
};
Dependent Base Classes
Not considered during name lookup
template<class T, class Compare>
class PQV : public vector<T> {
Compare comp;
public:
PQV(Compare cmp = Compare()) : comp(cmp) {
make_heap(this->begin(),this->end(), comp);
}
const T& top() const { return this->front(); }
void push(const T& x) {
this->push_back(x);
push_heap(this->begin(),this->end(), comp);
}
void pop() {
pop_heap(this->begin(),this->end(), comp);
this->pop_back();
}
};
Moral
• “x” is not always the same as “this->x”!
• In tempaltes, anyway
Template Metaprogramming
• Compile-time Computation!
– whoa!
• Has been proven to be “Turing complete”
– means that theoretically, you can do anything at
compile time
– in practice, it’s used for custom code generation,
compile-time assertions, and numerical libraries
Runtime Factorial
• The compiler can do integer arithmetic
– Common in legacy preprocessor operations
• Compile-time recursion:
– Use a primary template for the recursion
• Uses in turn an instantiation of itself
– A specialization stops the recursion at the base case
• See factorial.cpp
Runtime binary-to-decimal conversion
(from David Abrahams)
template<unsigned long N>
struct binary {
static const unsigned int value =
binary<N/10>::value << 1 | N%10;
};
// A (full/total) specialization to stop the recursion.
template<>
struct binary<0> {
static const unsigned int value = 0;
};
int main()
cout <<
cout <<
cout <<
}
{
binary<101>::value << endl;
binary<1001101110>::value << endl;
binary<11100111>::value << endl;
// 5
// 622
// 231
Traits
• A way to store type-specific code for templates
– “Compile-time polymorphism”
– Use separate template specializations for each type
• Examples:
– std::numeric_limits
– std::char_traits
std::numeric_limits
template<class T> class numeric_limits {
public:
static const bool is_specialized = false;
static T min() throw(); // for float, etc.
static T max() throw();
static const int digits = 0;
static const int digits10 = 0;
static const bool is_signed = false;
static const bool is_integer = false;
static const bool is_exact = false;
static const int radix = 0;
static T epsilon() throw();
... };
float eps = numeric_limit<float>::epsilon();
IEEE Traits
template<typename T>
struct IEEE_traits {};
template<>
struct IEEE_traits<float>
{
typedef float FType;
enum {
nbytes = sizeof(float),
nbits = nbytes*8,
exp_bits = 8,
bias = 127
};
};
template<>
struct IEEE_traits<double>
{
typedef double FType;
enum {
nbytes = sizeof(double),
nbits = nbytes*8,
exp_bits = 11,
bias = 1023
};
};
Using IEEE_Traits
template<typename FType>
bool isinfinity(FType x) {
return exponent(x) == IEEE_traits<FType>::bias+1 &&
fraction(x) == FType(0);
}
template<typename FType>
bool isnan(FType x) {
return exponent(x) == IEEE_traits<FType>::bias+1 &&
fraction(x) != 0;
}
Policies
• Similar to Traits
– But the emphasis is on functionality (not data)
– Template arguments represent implementation
strategies
• Examples:
– STL sequences
– Allocators
– Alexandrescu Singleton
C++’s Container Adapters
Policies in Action
• queue, stack, priority_queue
• Implemented with an underlying sequence data
structure
– vector, deque, or list or roll your own
• You glue them together at compile time:
– queue<int, list<int> >
– Default storage “policy” is deque<T>
Allocators
• Recall the definition of std::vector:
template<class T,
class Allocator = allocator<T> >
class vector;
• std::allocator<T> is a memory management
policy
– It uses new and delete
– But you can provide your own custom allocator class
• A pool allocator, say
Alexandrescu’s Singleton
• Has 4 template parameters:
–
–
–
–
The class to “singleton-ize”
Storage Policy
Lifetime Policy
Threading Policy
• Singleton<MyClass,CreateStatic,NoDestroy> x;
– Defaults to SingleThreaded
Counting Objects
• Can use a static data member that tracks the
object count
• Constructors increment
• Destructors decrement
Counting Non-template objects
// This is C++ 101:
class CountedClass {
static int count;
public:
CountedClass() { ++count; }
CountedClass(const CountedClass&) { ++count; }
~CountedClass() { --count; }
static int getCount() { return count; }
};
int CountedClass::count = 0;
How not to share Counting Code
class Counted {
static int count;
public:
Counted() { ++count; }
Counted(const Counted&) { ++count; }
~Counted() { --count; }
static int getCount() { return count; }
};
int Counted::count = 0;
// All derived classes share the same count!
class CountedClass : public Counted {};
class CountedClass2 : public Counted {};
The Solution
• We need a separate count for each class to be
counted
• We need to inherit from a different class for each
client class
• Hence, we need to use both inheritance (OOP)
and templates (compile-time polymorphism)
• See next slide
A Template Counter Solution
template<class T> class Counted {
static int count;
public:
Counted() { ++count; }
Counted(const Counted<T>&) { ++count; }
~Counted() { --count; }
static int getCount() { return count; }
};
template<class T> int Counted<T>::count = 0;
// Curious class definitions!!!
class CountedClass : public Counted<CountedClass>
{};
class CountedClass2 : public Counted<CountedClass2>
{};
A Curiously Recurring Template
Pattern (CRTP)
• A class, T, inherits from a template that
specializes on T!
• class T : public X<T> {…};
• Only valid if the size of X<T> can be determined
independently of T
– i.e., during Phase 1
Singleton via CRTP
• Inherit Singleton-ness
• Uses Meyers’ static singleton object approach
– Since nothing non-static is inherited, the size is known
at template definition time
• Protected constructor, destructor
• Disables copy/assign
• See next slide
Singleton via CRTP
// Base class – encapsulates singleton-ness
template<class T> class Singleton {
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
protected:
Singleton() {}
virtual ~Singleton() {}
public:
static T& instance() {
static T theInstance; // Meyers' Singleton
return theInstance;
}
};
Making a Class a Singleton
// A sample class to be made into a Singleton
class MyClass : public Singleton<MyClass> {
int x;
protected:
friend class Singleton<MyClass>; // to create it
MyClass() { x = 0; }
public:
void setValue(int n) { x = n; }
int getValue() const { return x; }
};
int main() {
MyClass& m = MyClass::instance();
cout << m.getValue() << endl;
m.setValue(1);
cout << m.getValue() << endl;
}
A Simple Class Template
• How can we add a stream inserter?
– ostream& operator(ostream&, const Box<T>&);
template<class T> class Box {
T t;
public:
Box(const T& theT) : t(theT) {}
};
This Won’t Work!
Templates Are Different
template<typename T>
class Box {
T value;
public:
Box(const T& t) { value = t; }
friend ostream& operator<<(ostream&, const Box<T>&);
};
template<typename T>
ostream& operator<<(ostream os, const Box<T> b) {
return os << b.value;
}
What’s the Problem?
• The inserter is not a template
– But it uses a template argument (T)
– This is a problem since it’s not a member function
– Our operator<<( ) must be a template
• What do we want?
– We want a distinct specialization for each T
– But it can’t be a member template!
• That would introduce an independent template parameter
• Aargh!
Solution 1
• There are special rules for friend function
templates to class templates
– Use angle brackets in the declaration of the friend
• Can be empty if the function parameters are sufficient to
deduce the template arguments
– But… the friend must be previously declared
• An exception to the usual automatic assumption that the
function is in the enclosing scope
• This also requires a forward declaration of Box
• See Next Slide
Friend Function Templates
// Forward declarations
template<class T> class Box;
template<class T> ostream& operator<<(ostream&,
const Box<T>&);
template<class T>
class Box {
T value;
public:
Box(const T& t) { value = t; }
friend ostream& operator<< <>(ostream&, const Box<T>&);
};
template<class T>
ostream& operator<<(ostream& os, const Box<T>& b)
{
return os << b.value;
}
Solution 2
template<typename T>
class Box {
T value;
public:
Box(const T& t) { value = t; }
friend ostream& operator<<(ostream& os, const Box<T>& b)
{
return os << b.value;
}
};
Making New Friends
• Define body of operator<< in situ
– (i.e.,inside of Box)
• Such an operator<< is not technically a template!
– No angle brackets are used
• A new ordinary function is created for each
specialization of Box!
– Names must be suitably mangled
– Like inner classes in Java
Had Enough?
• There’s still more to cover!
• But let’s stop here.
Download