COMP6771 Advanced C++ Programming
Week 8.1 Advanced Templates
1
demo801-default.h
demo801-default.cpp
Default Members
1 #include
3 template
4 class stack {
5 public:
6 stack();
7 ~stack();
8 auto push(T&) -> void;
9 auto pop() -> void;
10 auto top() -> T&;
11 auto top() const -> T const&;
12 static int num_stacks_;
13
14 private:
15 CONT stack_;
16 };
17
18 template
19 int stack
20
21 template
22 stack
23 num_stacks_++;
24 }
25
26 template
27 stack
28 num_stacks_–;
29 }
We can provide default arguments to template types (where the defaults themselves are types)
It means we have to update all of our template parameter lists
1 2 3 4 5 6 7 8 9
10
#include
#include “./demo801-default.h”
auto main() -> int {
auto fs = stack
}
stack
std::cout << stack
Explicit specialisation:
Describing the template for a specific, non-generic type std::string
int
3.1
When to specialise
You need to preserve existing semantics for something that would not otherwise work
std::is_pointer is partially specialised over pointers You want to write a type trait
std::is_integral is fully specialised for int, long, etc.
There is an optimisation you can make for a specific type
std::vector
3.2
When not to specialise Don’t specialise functions
A function cannot be partially specialised
Fully specialised functions are better done with overloads
Herb Sutter has an article on this
http://www.gotw.ca/publications/mill17.htm
You think it would be cool if you changed some feature of the class for a specific type
People assume a class works the same for all types
Don’t violate assumptions!
3.3
Our Template
Here is our stack template class
stack.h stack_main.cpp
1 #include
2 #include
3 #include
4
5 template
6 class stack {
7 public:
8 auto push(T t) -> void { stack_.push_back(t); }
9 auto top() -> T& { return stack_.back(); }
10 auto pop() -> void { stack_.pop_back(); }
11 auto size() const -> int { return stack_.size(); };
12 auto sum() -> int {
13 return std::accumulate(stack_.begin(), stack_.end(), 0);
14 }
15 private:
16 std::vector
17 };
1 auto main() -> int {
2 int i1 = 6771;
3 int i2 = 1917;
4
5 stack
6 s1.push(i1);
7 s1.push(i2);
8 std::cout <<
9 std::cout <<
10 std::cout <<
11 }
s1.size() << " ";
s1.top() << " ";
s1.sum() << "\n";
3.4
Partial Specialisation
In this case we will specialise for pointer types. Why do we need to do this?
You can partially specialise classes
You cannot partially specialise a particular function of a class in isolation
The following a fairly standard example, for illustration purposes only. Specialisation is designed to refine a generic implementation for a specific type, not to change the semantic.
1 2 3 4 5 6 7 8 9
10 11 12 13 14
template
class stack
public:
auto push(T* t) -> void { stack_.push_back(t); }
auto top() -> T* { return stack_.back(); }
auto pop() -> void { stack_.pop_back(); }
auto size() const -> int { return stack_.size(); };
auto sum() -> int{
} private:
};
return std::accumulate(stack_.begin(),
stack_.end(), 0, [] (int a, T *b) { return a + *b; });
std::vector
1 #include “./demo802-partial.h” 2
3 auto main() -> int {
4 auto i1 = 6771;
5 auto i2 = 1917;
6
7 auto s1 = stack
8 s1.push(i1);
9 s1.push(i2);
10 std::cout <<
11 std::cout <<
12 std::cout <<
13 }
s1.size() << " ";
s1.top() << " ";
s1.sum() << "\n";
demo802-partial.h
demo802-partial.cpp
3.5
Explicit Specialisation
Explicit specialisation should only be done on classes. std::vector
std::vector
1 2 3 4 5 6 7 8 9
10
11
12
13
14
15
16
#include
template
struct is_void {
static bool const val = false;
};
template<>
struct is_void
static bool const val = true;
};
auto main() -> int {
std::cout << is_void
#include
std::cout << std::numeric_limits
static auto min() -> int { return -INT_MAX – 1; }
template
struct numeric_limits {
};
template <>
struct numeric_limits
10
11 template <>
12 struct numeric_limits
13 static auto min() -> float { return -FLT_MAX – 1; }
14 }
This is what
4.1
Type Traits
Traits allow generic template functions to be parameterised
1 2 3 4 5 6 7 8 9
10
11
12
13
14
15
16
17
18
19
20
#include
#include
#include
T findMax(const std::array
}
T largest = std::numeric_limits
for (auto const& i : arr) {
if (i > largest)
largest = i;
}
return largest;
auto main() -> int {
auto i = std::array
std::cout << findMax
std::cout << findMax
template
struct is_void {
};
static const
bool val = false;
{
bool val = true;
is_void
struct is_void
static const
};
auto main() -> int {
std::cout <<
std::cout <<
}
1 2 3 4 5 6 7 8 9
10
11
12
13
14
15
16
#include
template
struct is_pointer {
static const bool val = false;
};
template
struct is_pointer
static const bool val = true;
};
auto main() -> int {
std::cout << is_pointer
#include
template
auto testIfNumberType(T i) -> void {
10 else { 11
12
13 }
if (std::is_integral
std::cout << i << " is a number"
<< "\n";
std::cout << i << " is not a number"
<< "\n";
14 } 15
16 auto main() ->
17 auto i
18 auto l
19 auto d
20 testIfNumberType(i);
21 testIfNumberType(l);
22 testIfNumberType(d);
23 testIfNumberType(123);
24 testIfNumberType(“Hello”);
25 auto s = “World”;
26 testIfNumberType(s);
27 }
int {
= int{6};
= long{7};
= double{3.14};
demo807-typetraits4.cpp
4.4
Variadic Templates
These are the instantiations that will have been generated
1 #include
2 #include
3
4 template
5 auto print(const T& msg) -> void {
6 7} 8
9
std::cout << msg << " ";
10
11
12
13
14
15
16
17
18
19
20
template
auto print(A head, B… tail) -> void {
print(head);
print(tail…);
}
auto main() -> int {
print(1, 2.0f);
}
std::cout << "\n";
print(1, 2.0f, "Hello");
std::cout << "\n";
9
10
11
12
13
14
15
16
17
18
19
20
21
auto print(float b, const char* c) -> void {
print(b);
print(c);
auto print(int const& a) -> void {
std::cout << a << " ";
}
auto print(int a, float b, const char* c) -> void {
print(a);
1 auto print(const char* const& c) -> void { 2 std::cout << c << " ";
3}
4
5 auto print(float const& b) -> void { 6 std::cout << b << " ";
7}
8
}
}
print(b, c);
demo808-variadic.cpp
5
Member Templates
Sometimes templates can be too rigid for our liking: Clearly, this could work, but doesn't by default
1 #include
3 template
4 class stack {
5 public:
6 auto push(T& t) -> void { stack._push_back(t); }
7 auto top() -> T& { return stack_.back(); }
8 private:
9 std::vector
10 };
11
12 auto main() -> int {
13 auto is1 = stack
14 is1.push(2);
15 is1.push(3);
16 auto is2 = stack
17 auto ds1 =
18 stack
19 }
6.1
Member Templates
Through use of member templates, we can extend capabilities
1 #include
3 template
4 class stack {
5 public:
6 explicit stack() {}
7 template
8 stack(stack
9 auto push(T t) -> void { stack_.push_back(t); }
10 auto pop() -> T;
11 auto empty() const -> bool { return stack_.empty(); }
12 private:
13 std::vector
14 };
15
16 template
17 T stack
18 T t = stack_.back();
19 stack_.pop_back();
20 return t;
21 }
22
23 template
24 template
25 stack
26 while (!s.empty()) {
27 stack_.push_back(static_cast
28 }
29 }
1 auto main() -> int {
2
3
4
5
6
7
8 9}
auto is1 = stack
is1.push(2);
is1.push(3);
auto is2 = stack
auto ds1 =
stack
// until we do the changes on the left
demo809-membertemp.cpp
6.2
Template Template Parameters
Previously, when we want to have a Stack with templated container type we had to do the following:
1 template
What is the issue with this?
Ideally we can just do:
1 #include
2 #include
3
4 auto main(void) -> int {
5 stack
6 s1.push(1);
7 s1.push(2);
8 std::cout << "s1: " << s1 << "\n";
9
10 stack
11 s2.push(1.1);
12 s2.push(2.2);
13 std::cout << "s2: " << s2 << "\n";
14 //stack
15 }
1 #include
2 #include
3
4 auto main(void) -> int {
5 stack
6 s1.push(1);
7 s1.push(2);
8 std::cout << "s1: " << s1 << std::endl;
9
10 stack
11 s2.push(1.1);
12 s2.push(2.2);
13 std::cout << "s2: " << s2 << std::endl;
14 }
7.1
Template Template Parameters
1 #include
2 #include
3
4 template
5 class stack {
6 public:
7 auto push(T t) -> void { stack_.push_back(t); }
8 auto pop() -> void { stack_.pop_back(); }
9 auto top() -> T& { return stack_.back(); }
10 auto empty() const -> bool { return stack_.empty(); }
11 private:
12 CONT stack_;
13 };
1 auto main(void) -> int {
2 stack
3 int i1 = 1;
4 int i2 = 2;
5 s1.push(i1);
6 s1.push(i2);
7 while (!s1.empty()) {
8 std::cout << s1.top() << " ";
9 s1.pop();
10 }
11 std::cout << "\n";
12 }
1 #include
2 #include
3 #include
4
5 template
6 class stack {
7 public:
8 auto push(T t) -> void { stack_.push_back(t); }
9 auto pop() -> void { stack_.pop_back(); }
10 auto top() -> T& { return stack_.back(); }
11 auto empty() const -> bool { return stack_.empty(); }
12 private:
13 CONT
14 };
1
2
3
4
5
6
7 8}
#include
#include
auto main(void) -> int {
auto s1 = stack
s1.push(1);
s1.push(2);
demo810-temptemp.cpp
7.2
Template Argument Deduction
Template Argument Deduction is the process of determining the types (of type parameters) and the values of nontype parameters from the types of function arguments.
type paremeter non-type parameter
1
2
3
4
5
6}
7 return min; 8}
template
T findmin(const T (&a)[size]) {
T min = a[0];
for (int i = 1; i < size; i++) {
if (a[i] < min) min = a[i];
call parameters
8.1
Implicit Deduction
Non-type parameters: Implicit conversions behave just like normal type conversions
Type parameters: Three possible implicit conversions ... others as well, that we won't go into
1 // array to pointer
2 template
3 f(T* array) {}
4
5 int a[] = { 1, 2 };
6 f(a);
1 // conversion to base class
2 // from derived class
3 template
4 void f(base
5
6 template
7 class derived : public base
8 derived
9 f(d);
1 // const qualification
2 template
3 f(const T item) {}
4
5 int a = 5;
6 f(a); // int => const int;
8.2
Explicit Deduction
If we need more control over the normal deduction process, we can explicitly specify the types being passed in
1 template
3 4} 5
6 auto main() -> int {
7 auto i = int{0};
8 auto d = double{0};
9 min(i, static_cast
10 // min
11 min(static_cast
12 min
13 }
return a < b ? a : b;
demo811-explicitdeduc.cpp
8.3
Feedback
9