generic_programming_intro

advertisement
Generic Programming
Johan Torp
Agenda
• GP introduction
• GP in perspective
• Questions
* GP = Generic programming in C++
What is GP?
• Algorithmic GP
• GP techniques & patterns in C++
Polymorphism
class BarInterface { virtual void bar() = 0; };
void polymorphicFunction(BarInterface& t) {
...
t.bar();
...
}
template<class T>
void polymorphicFunction(T& t) {
...
t.bar();
...
}
GP interface is called concept
// MyBarConcept
//
// Has a memberfunction called bar()
// ... more assumptions ...
// T must fulfill MyBarConcept
template<class T>
void polymorphicFunction1(T& bar);
// T must fulfill MyBarConcept
template<class T>
void polymorphicFunction200(T& bar);
class MyBar {
void bar();
};
Example concepts
Concept
Valid expressions
DefaultConstructible
Copyable
Assignable
Addable
Convertible to OtherType
OutputStreamable
BarConcept
T t;
T t2(t1);
t1 = t2
t1+t2;
static_cast<OtherType>(t);
stream << t;
t.bar();
Require as little as possible for maximal genericity
Concepts are type requirements
• Valid expressions, pre/post conditions & semantics
• Additionally: Invariants, associated types,
complexity guarantees, etc
Concepts in C++ are expressed in documentation
Types which fulfill concept C are said to model C
Refine new concepts from old ones by adding additional
requirements
Input Iterator
Description
An Input Iterator is an iterator that may be dereferenced to refer to ...
Refinement of
Trivial Iterator
Associated Types
Value type - The type obtained by dereferencing ...
Distance type - A signed integral type used to ...
Valid expressions
Dereference
*i
Pre-increment
++i
Expression semantics
Dereference
Will return a reference to the accessed value type
Pre-increment
Will ...
Complexity guarantees
All operations are amortized constant time
Template instantiation
class OneBar { void bar() };
class AnotherBar { void bar() };
Source1.cpp:
polymorphicFunction(int(123));
Source2.cpp:
polymorphicFunction(OneBar());
polymorphicFunction(OneBar());
polymorphicFunction(AnotherBar());
Source3.cpp:
polymorphicFunction(OneBar());
Generalize memcpy
void* memcpy(void* region1, const void* region2,size_t n);
• Only reads contigous memory
• Only writes to contigous memory
• Only byte-wise copying
Minimal requirements of copying
•
•
•
•
traverse through source sequence
access source elements
copy elements to destination
know when to stop
STL version of copy
template <class InputIterator, class OutputIterator>
OutputIterator
copy(InputIterator first, InputIterator pastLast, OutputIterator result)
{
while (!(first == pastLast)) // Don't require != operator
*result++ = *first++;
return result;
}
How pointless?!
• Real std::copy is optimized for different types
• Solves optimized generic copying once and for all
C++ language features supporting GP
Dispatching features
• Inheritance
• Templates
• Namespaces & argument dependent lookup (ADL)
• Function & operator overloading
• Implicit type conversion
• SFINAE
• [Partial] template specialization
Other useful language features
• Dependent types
• Template type deduction
• Template non-type arguments (compile-time integers, bools, etc)
• Template template arguments
GP techniques & patterns
•
•
•
•
•
•
•
•
•
type traits
mix-in classes
policy based design
object generators
enable if
tag dispatching
type erasure
lazy data views
concept and archetype checking
GP related programming models:
• template metaprogramming
• expression templates to create DSELs
• macro metaprogramming
GP in practice
• Generalize patterns / boiler plate into generic classes
• Library based programming models
GP problems
• Compilation & link time
• Code size
• Language flaws
How much GP can we afford?
Maybe we can use more GP because:
•
•
•
•
•
•
Compilers have improved
Well modularized => recompile less
Master/unity builds lessens template bloat
Stability & higher abstraction level allows faster iteration too
Explicit template instantiation can lessen template bloat
Replacing boilerplate interfaces with type erasure gives
similar run-time performance
GP flaws fixed in C++0x
•
•
•
•
•
•
•
•
Concept code support
True variadic templates
Type deduction and type inference of named variables
Rvalue references
Preventing template instantiation
Axioms
Object construction improvements
... and lots more!
Compilers are continously adding C++0x features
GP vs OOP: Efficiency and compilation
GP more efficient run-time
OO code requires less compilation
GP vs OOP: Dependencies
• Concrete OO classes and polymorphic code are coupled via
the interface
• A type might fulfill/model a concept. Polymorphic code only
depends on concept via documentation
A.k.a. : ad-hoc polymorphism vs parametric polymorphism
GP vs OOP: Customization points
Mix-in classes change interface, state, data layout, etc
template <class MixIn>
class Foo : public MixIn {...};
Type traits & tags can non-intrusively add static information
template<class T> struct iterator_traits<T*>
{ typedef random_access_iterator_tag iterator_category;};
GP vs OOP: Orthogonality
template<class Aspect1, class Aspect2, class
Aspect3>
class TweakableGPClass{ ... };
class TweakableOOClass{
TweakableOOClass(Aspect1&, Aspect2&, Aspect3&);
private:
Aspect1& m_a1; ...
};
Composition problems
• Boiler-plate
• Run-time costs (vtable calls, method forwarding, extra state)
GP vs OOP: Dispatch
• OOP is only polymorphic on one type - the object type
• GP is polymorphic on all parameter types
• GP also allow complex dispatch logic!
// Default fallback, unless no other function matches better
template<class Object1, class Object2>
void handleCollision(Object1&, Object2&);
// Any object going into a black hole disappears
template<Object1>
void handleCollision(Object1&, BlackHole&);
// Unless it's another black hole -- then we get
// a super nova explosion instead. Yay!
template<>
void handleCollision(BlackHole&, BlackHole&);
// Missiles which hit anything damagable inflicts damage
template<class Object2>
enable_if<void, isDamagable<Object2>::value>
handleCollision(Missile&, Object2&);
GP vs OOP: Simplicity
• More power = more ways to shoot ourselves in the foot
• OO is less abstract
GP is best suited to build generic libraries - which in turn
provide simple and limited programming models
C++ multiparadigm programming
• Object orientation
• Generic programming
• Procedural programming
GP and dynamically typed languages
def foo(t):
"""t must model bar"""
...
t.bar()
...
• Compiled separately, extremely fast iteration time
• Late binding can provide poor runtime performance
• Nothing checked statically, rely on interpreters & unit tests
A reflection on reflection
Reflection allows a program to observe and modify its own
structure and behavior at runtime
• GP dispatch techniques are similar to run-time reflection
• GP allows static analysis => understandable behaviour
GP is generative
Macro > Template instantiation > Compilation
• Work on multiple "compilation levels" in the same file at the
same time
Code generation
• Raise abstraction level without runtime penalty
• Pay in terms of build system complexity
• Identify boiler plate & patterns and generate them
Summary
•
•
•
•
•
•
•
•
•
•
Concepts are type requirements
Template instantiation
Algorithmic GP
GP techniques
GP problems
GP classes are highly customizable and reusable
GP vs OOP
GP is useful for core libraries & algorithms
Dynamic programming languages & reflection
Code generation
Questions?
Download