Generic programming & library development Today: Generic - - PowerPoint PPT Presentation

generic programming library development
SMART_READER_LITE
LIVE PREVIEW

Generic programming & library development Today: Generic - - PowerPoint PPT Presentation

Generic programming & library development Today: Generic programming techniques power of templates design patterns Lecturer: Jyrki Katajainen Some of these slides are from Kenny Erleben Course home page:


slide-1
SLIDE 1

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (1)

Generic programming & library development

Today: Generic programming techniques

  • power of templates
  • design patterns

Lecturer: Jyrki Katajainen Some of these slides are from Kenny Erleben Course home page: http://www.diku.dk/forskning/performance-engineering/ Generic-programming/

slide-2
SLIDE 2

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (2)

Polymorphism

The word polymorphism means “the ability to have many forms”. Parametric polymorphism: C++ templates Inclusion polymorphism: C++ virtual functions Overloading: C++ function overloading including partial specializa- tion Coercion: C++ built-in or user defined conversion operators or con- structors to coercion

slide-3
SLIDE 3

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (3)

Dynamic polymorphism: base class

A traditional approach where common behaviour is defined in an ab- stract base class

class Shape { public: virtual int id() const = 0; virtual std::string type() const = 0; // ... };

slide-4
SLIDE 4

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (4)

Dynamic polymorphism: derived classes

class Sphere : public Shape { public: virtual int id() const { return 1; } virtual std::string type() const { return "sphere"; } }; class Box : public Shape { public: virtual int id() const { return 2; } virtual std::string type() const { return "box"; } };

and so on... (Question: Why are all member functions virtual?)

slide-5
SLIDE 5

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (5)

Dynamic polymorphism: test functions

Let us define some functions that operate on different shapes

void pair_test(Shape const* A, Shape const* B) { std::cout << "collisiondetection:" << (*A).type() << "and" << (*B).type() << std::endl; }

Or a little more exotic

void collision(std::vector<Shape*> const& shapes) { for(unsigned i = 0; i < shapes.size(); ++i) { for(unsigned j = i + 1; j < shapes.size(); ++j) { pair_test(shapes[i], shapes[j]); } } }

slide-6
SLIDE 6

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (6)

Dynamic polymorphism: usage

Let us try our example functions

int main() { Sphere s0; Sphere s1; Box b0; Box b1; Box b2; pair_test(&b2, &s1); std::vector<Shape*> shapes; shapes.push_back(&s0); shapes.push_back(&s1); shapes.push_back(&b0); shapes.push_back(&b1); shapes.push_back(&b2); collision(shapes); }

slide-7
SLIDE 7

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (7)

Dynamic polymorphism: summary

  • The interface is bounded,
  • the binding of interfaces is done at run time (dynamically), and
  • it is easy to create heterogeneous containers.
  • What if we want to extend with a new shape?

class Prism : public Shape...

  • What if we want to extend with a new function?

virtual point centre_of_gravity() const = 0;

slide-8
SLIDE 8

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (8)

Static polymorphism

Let us try to use templates instead of inheritance

class Sphere { public: int id() const { return 1; } std::string type() const { return "sphere"; } }; class Box { public: int id() const { return 2; } std::string type() const { return "box"; } };

slide-9
SLIDE 9

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (9)

Static polymorphism: testing

We also need to rewrite our test functions

template <typename Shape1, typename Shape2> void pair_test(Shape1 const& A, Shape2 const& B) { std::cout << "collisiondetection:" << A.type() << "and" << B.type() << std::endl; }

and we can now use it

int main() { ... pair_test(b0, s1); ... pair_test(b2, s2); }

slide-10
SLIDE 10

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (10)

Static polymorphism: falling short

What about?

void collision(std::vector<Shape*> const& shapes) { for(unsigned i = 0; i < shapes.size(); ++i) { for(unsigned j = i + 1; j < shapes.size(); ++j) { pair_test(shapes[i], shapes[j]); } } }

  • Sorry, this is impossible; we cannot handle this transparently!

std::vector<Shape*> const& shapes

slide-11
SLIDE 11

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (11)

Static polymorphism: summary

  • The interface is unbounded,
  • the binding of interfaces is done at compile time (statically), and
  • one cannot create heterogeneous containers.
  • What if we want to extend with a new shape?

class Prism

  • What if we want to extend with a new function?

bool centre_of_gravity() const { ... };

slide-12
SLIDE 12

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (12)

Design pattern: bridge

Decouple an abstraction from its implementation so that the two can vary independently.

  • Possible to provide several implementations with the same inter-

face.

  • Clients can select the best implementations for their purposes.
  • Implementations can be smaller than the bridge (that is, pieces

identical to all implementations are implemented at the bridge).

slide-13
SLIDE 13

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (13)

Bridge pattern implemented using inheritance

  • perationA();
  • perationB();
  • perationA();
  • perationB();
  • perationA();
  • perationB();
  • perationC();

virtual operationA() = 0; virtual operationB() = 0; R R* realization; B implemention 2 implemention 1

Source: [Vandevoorde and Josuttis 2003, §14.4]

slide-14
SLIDE 14

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (14)

Bridge pattern implemented using templates

  • perationA();
  • perationB();
  • perationA();
  • perationB();
  • perationA();
  • perationB();
  • perationC();

R B implemention 2 implemention 1 R realization;

Source: [Vandevoorde and Josuttis 2003, §14.4]

slide-15
SLIDE 15

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (15)

Stack bridge versus stack kernel

template < typename V, typename A = std::allocator<V>, typename R = cphstl::list_stack<V, A> > class stack { public: ... size_type size() const; bool empty() const; protected: R kernel; }; template <typename V, typename A, typename R> typename stack<V, A, R>::size_type stack<V, A, R>::size() const { return kernel.size(); } template < typename V, typename A = std::allocator<V>, typename R = std::list<V, A> > class list_stack { public: ... typedef std::size_t size_type; ... size_type size() const; ... };

slide-16
SLIDE 16

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (16)

Design pattern: iterator

Provide a way to access the elements of a container sequentially without exposing its underlying representation.

  • In the C++ standard library, iterators come in several different

flavours: locators (or trivial iterators), input iterators, output iter- ators, forward iterators, bidirectional iterators, and random-access iterators.

  • Iterators are generalizations of pointers.
slide-17
SLIDE 17

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (17)

Iterators as the clue

Source: David R. Musser, et al., STL Tutorial and Reference Guide: C++ Program- ming with the Standard Template Library, 2nd Edition, Addison-Wesley (2001)

slide-18
SLIDE 18

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (18)

Generic function accumulate

Let n be a non-negative integer and xi a value of type V for i ∈ {0, 1, . . . , n − 1}. Assume that operator+ is defined for V. Function accumulate computes n−1

i=0 xi for any sequence of elements

  • f type V.

#include <iterator> // defines std::iterator_traits template <typename I> typename std::iterator_traits<I>::value_type accumulate(I p, I q) { typedef typename std::iterator_traits<I>::value_type V; V total = V(); while (p ≡ q) { total += *p; ++p; } return total; }

slide-19
SLIDE 19

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (19)

Facilities available at compile time

  • 1. Template parameters can be types.
  • 2. Template parameters can be integral values (e.g. of type int,

short, char, bool, or an enumeration type).

  • 3. Template parameters can be templates, pointers, or functions.
  • 4. sizeof can be evaluated at compile time.

Surprisingly, the template mechanisms available in C++ can be exploit- ed as a fully-fledged programming language.

slide-20
SLIDE 20

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (20)

Compile-time “assignments”

typedefs are used to create new type aliases for other types. Member types can be used to propagate information between com- ponents at compile time. Compile-time mechanism

template <typename size_type> class some_class { public: typedef size_type capacity_type; }; ... typedef unsigned int natural; typedef typename some_class<natural>:: capacity_type T;

Run-time mechanism

class some_class { public: some_class(std::string const size_type) : capacity_type(size_type) { } std::string const capacity_type; }; ... std::string const natural = "unsignedint"; some_class object("natural"); std::string const T = object.capacity_type;

slide-21
SLIDE 21

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (21)

Compile-time “variables”

The “variables” of static C++ code are type names and integral con-

  • stants. After the initialization the value cannot be changed. If you

need a new type or value, you simply create a new type. Just as in functional programming, static C++ code uses symbolic names rather than true variables. That is, all the compile-time variables refer to true constants.

slide-22
SLIDE 22

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (22)

Compile-time “functions”

B: a set of Boolean values, i.e. {false, true} S: a set of strings T : a set of type names Traits: T → T × T × . . . Static assertions: B → S Compile-time reflection: T → B Type functions: T → T Compile-time if: B → T

slide-23
SLIDE 23

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (23)

Generalized accumulate

Generalize accumulate such that it computes n−1

i=0 xi for any associa-

tive operation ⊕.

template <typename I> typename std::iterator_traits<I>::value_type accumulate(I, I);

Problem 1: Return type can be too small for the accumulated value. Problem 2: How to parameterize accumulate with ⊕? Problem 3: What to return if n = 0? That is, what is the zero value for ⊕? Problem 4: Function templates cannot have default template argu- ments.

slide-24
SLIDE 24

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (24)

Compile-time “structs”

Traits are used to bundle different types together as their members. Traits represent natural additional properties of a template parameter.

template <typename T> class accumulation_traits; template <> class accumulation_traits<bool> { public: typedef int return_type; }; template <> class accumulation_traits<char> { public: typedef int return_type; }; template <> class accumulation_traits<short> { public: typedef int return_type; }; template <> class accumulation_traits<int> { public: typedef long return_type; }; template <> class accumulation_traits<float> { public: typedef double return_type; };

slide-25
SLIDE 25

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (25)

Design pattern: strategy

Define a family of algorithms, encapsulate each one, and make them

  • interchangeable. Strategy pattern lets the algorithm vary indepen-

dently from clients that use it. Policies represent configurable behaviour for generic functions and types (often with some commonly used defaults).

template < typename V, typename T = accumulation_traits<V> > class sum { public: typedef typename T::return_type R; void accumulate(R& total, V const& v) { total += v; } }; template < typename V, typename T = accumulation_traits<V> > class zero { public: typedef typename T::return_type R; R initialize() { return R(); } };

slide-26
SLIDE 26

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (26)

Policy-based implementation of accumulate

template < typename I, typename T = accumulation_traits<typename std::iterator_traits<I>::value_type>, typename S = sum<typename std::iterator_traits<I>::value_type, T>, typename Z = zero<typename std::iterator_traits<I>::value_type, T> > class accumulation { public: typedef typename T::return_type return_type; return_type accumulate(I p, I q) { Z initializer; S accumulator; return_type total = initializer.initialize(); while (p ≡ q) { accumulator.accumulate(total, *p); ++p; } return total; } };

slide-27
SLIDE 27

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (27)

Testing policy-based accumulate

int main() { int numbers[] = {1, 2, 3, 4, 5}; unsigned int n = sizeof(numbers) / sizeof(numbers[0]); typedef accumulation<int*> A; A adder; A::return_type average = adder.accumulate(&numbers[0], &numbers[n]) / n; dynamic_assert(average ≡ A::return_type(3)); typedef accumulation_traits<int> T; typedef accumulation<int*, T, product<int, T>, one<int, T> > M; M multiplier; M::return_type product = multiplier.accumulate(&numbers[0], &numbers[n]); dynamic_assert(product ≡ (1 * 2 * 3 * 4 * 5)); return 0; }

slide-28
SLIDE 28

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (28)

Static assertions

namespace cphstl { template <bool> class compile_time_checker { public: compile_time_checker(...) { } }; template <> class compile_time_checker<false> { }; #define static_assert(condition, message) { \ class ERROR_##message { \ }; \ typedef cphstl::compile_time_checker<(condition)> type; \ type temp = type(ERROR_##message()); \ (void) sizeof(temp); \ } }

slide-29
SLIDE 29

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (29)

Substitution failure is not an error (SFINAE)

#include "assert.h++" typedef char RT1; typedef struct { char a[2]; } RT2; template <typename T> RT1 test(typename T::string_length const*); template <typename T> RT2 test(...); #define has_member_type_string_length(T) \ (sizeof(test<T>(0)) ≡ 1) class yo_yo { public: typedef unsigned int string_length; }; int main() { static_assert(has_member_type_string_length(yo_yo), testing_yo_yo); static_assert(has_member_type_string_length(int) ≡ false, testing_int); }

slide-30
SLIDE 30

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (30)

More SFINAE

Or the trick can be encapsulated in a nice package:

template<typename T> class IsClass { private: typedef char one; // size = 1 byte typedef struct{char a[2] } two; // size = 2 byte template <typename C> static one test(int C::*); // only classes template <typename C> static two test(...); // anything else public: enum {yes = sizeof(IsClass<T>::test<T>(0)) ≡ 1}; enum {no = !yes}; };

slide-31
SLIDE 31

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (31)

SFINAE: usage

Now we can write

if (IsClass<T>::yes) { // do something with class } else { // do something with non-class }

Or we might want to write pretty readable code

template <typename T> bool is_class(T) { if (IsClass<T>::yes) { return true; } return false; }

So we can simply write

yo_yo dodah; if (is_class(dodah)) { ...

slide-32
SLIDE 32

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (32)

Curiously recurring template pattern (CRTP)

Templates and inheritance can also be used together.

template <typename Derived> class Base { public: ... }; template <typename T> class Child : public Base< Child<T> > { public: ... };

slide-33
SLIDE 33

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (33)

CRTP: usage

This can be useful for defining common interfaces without using an abstract base class.

template <typename Derived> class Base { public: void f() { Derived& self = static_cast<Derived&>(*this); self.f(); } bool g(int count) const { Derived const& self = static_cast<Derived const&>(*this); return self.g(count); } };

Now the compiler ensures that class Child implements f and g.

slide-34
SLIDE 34

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (34)

CRTP: a problem?

Not quite; what happens if

template <typename Derived> class Base { public: void f() { Derived& self = static_cast<Derived&>(*this); self.f(); } }; class Child : public Base<Child> { public: };

  • An infinite loop!
  • Oh, but shouldn’t the compiler tell us that we forgot to implement

f on Child?

slide-35
SLIDE 35

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (35)

CRTP: workarounds

Workaround 1: Use private inheritance.

class Child : private Base<Child> { public: };

Workaround 2: Avoid name clashes.

template<typename Derived> class Base { public: void f() { Derived& self = static_cast<Derived&>(*this); self.h(); } };

Workaround 3: Turn compiler warnings into errors.

slide-36
SLIDE 36

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (36)

Compile-time “if”

#include "assert.h++" template <bool condition, typename Then, typename Else> class IF { public: typedef Then RET; }; //specialization for condition ≡ false template <typename Then, typename Else> class IF<false, Then, Else> { public: typedef Else RET; }; int main() { static_assert(sizeof(IF<(1 + 2 > 4), char, int>::RET) ≡ sizeof(int), testing_IF); IF<(1 + 2 > 4), char, int>::RET i; //the type of i is int! return 0; }

slide-37
SLIDE 37

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (37)

Compile-time recursion

#include "assert.h++" template <int n> class Factorial { public: enum { RET = Factorial<n - 1>::RET * n }; }; // this template specialization terminates the recursion template <> class Factorial<0> { public: enum { RET = 1 }; }; int main() { static_assert(Factorial<7>::RET ≡ (1 * 2 * 3 * 4 * 5 * 6 * 7), testing_Factorial); return 0; }

slide-38
SLIDE 38

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (38)

Turing completeness

A language is Turing complete if it provides a conditional and a loop- ing construct. That is, the meta level of C++ can compute the same functions as a Turing machine.

slide-39
SLIDE 39

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (39)

Facilities missing

  • Varying number of template arguments is not (yet) supported

(cf. the ellipsis construction for run-time functions).

int printf(char const*, ...);

  • Types and integers can be manipulated at compile time, but not

floats or strings.

  • Syntax for compile-time computations is terrible!
slide-40
SLIDE 40

c

Performance Engineering Laboratory

Generic programming and library development, 29 April 2008 (40)

Research problem

C++ is a combination of three languages:

  • macro language inherited from C,
  • run-time language, and
  • compile-time language.

Design a language that

  • has the power of C++,
  • has a simple syntax,
  • is natural,
  • is minimal, and
  • can get equally many users as C++.