泛型程式设计(Generic Programming)

元智大学 89-2

课程备忘录 / 侯捷



■第1周:03/01 缺书

介绍课程,游戏规则,书籍评介,期末作业题目
GP (Generic Programming) and STL overview
★练习1:设定好你的编译环境,建议使用 console mode.
★练习2:试用 C runtime library 的 qsort() 写个程式
         感受一下 function pointer.
■第2周:03/08  书到 七折([Austern98], [Lippman98])
STL components review
●程式例1 试用 STL 的容器和演算法
#include<iostream>
#include<vector>
#include<set>
#include<algorithm>
#include<functional>
using namespace std;
void print(int i)
{
   cout << i << ' ';
}
int  main()
{
   {
   int i;
   int ia[10]={12,14,54,6,3,66,45,90,10,20};
   vector<int> iv(ia, ia+10);
   for_each(iv.begin(), iv.end(), print);
   cout << endl;
   sort(iv.begin(), iv.end());
   for_each(iv.begin(), iv.end(), print);
   cout << endl;
   set<int> is;
   is.insert(4);
   is.insert(3);
   is.insert(7);
   is.insert(0);
   is.insert(2);
   for_each(is.begin(), is.end(), print);
   cout << endl;
   }
   {
   int i;
   int ia[10]={12,14,54,6,3,66,45,90,10,20};
   vector<int> iv(ia, ia+10);
   for_each(iv.begin(), iv.end(), print);
   cout << endl;
   sort(iv.begin(), iv.end(), greater<int>() );
   for_each(iv.begin(), iv.end(), print);
   cout << endl;
   }
}
●程式例2 试用各种 STL components
#include <algorithm>
#include <functional>
#include <vector>
#include <iostream>
using namespace std;
int main()
{
  int ia[ 6 ] = { 27, 210, 12, 47, 109, 83 };
  vector<int, allocator<int> > vec( ia, ia+6 );
  cout << count_if(vec.begin(), vec.end(),
                   not1(bind2nd(less_equal<int>(), 40)));
  return 0;
}
// result : 4


■第3周:03/15
find() 泛型化,
运算子多载化 operator overloading.
●程式例1  find() 泛型化的最後结果
#include <iostream>
#include <list>
using namespace std;
template <typename I, typename T>
I find(I begin, I end, const t& value)
{
  // cout << "jjhou" << endl;
  while (begin != end && *begin != value)
     ++begin;
  return begin;
}
int main()
{
  int ia[5] = { 0,1,2,3,4};
  int* pi = find(ia, ia+5, 2);
  cout << *pi << endl;       // 2
  double da[5] = { 0,1,2,3,4};
  double* pd = find(da, da+5, 2.0);
  cout << *pd << endl;       // 2
  list<int> mylist(ia, ia+5);
  list<int>::iterator ite;
  ite = find(mylist.begin(), mylist.end(), 4);
  cout << *ite << endl;      // 4
}
★教训:如果 find() 介面如下
template <typename I, typename T>
I find(I begin, I end, T value)    // 注意最後一个叁数的型式
而非:
template <typename I, typename T>
I find(I begin, I end, const T& value)
会得到以下的可怕错误:
Borland C++ 5.4 for Win32 Copyright (c) 1993, 1999 Inprise Corporation
..\T1.CPP:
Error E2015 ..\T1.CPP 19: Ambiguity between
  'std::find<int *,int>(int *,int *,const int &)' and
  'find<int *,int>(int *,int *,int)' in function main()
Error E2015 ..\T1.CPP 24: Ambiguity between
  'std::find<double *,double>(double *,double *,const double &)' and
  'find<double *,double>(double *,double *,double)' in function main()
Error E2015 ..\T1.CPP 30: Ambiguity between
  'std::find<std::list<int,std::allocator<int> >::iterator,int>
    (std::list<int,std::allocator<int> >::iterator,
     std::list<int,std::allocator<int> >::iterator,const int &)' and
  'find<std::list<int,std::allocator<int> >::iterator,int>
    (std::list<int,std::allocator<int> >::iterator,
     std::list<int,std::allocator<int> >::iterator,int)' in function main()
原因是
template <typename I, typename T>
I find(I begin, I end, T value)
template <typename I, typename T>
I std::find(I begin, I end, const T& value)
形成模棱两可。见 C++ Primer, p521.
★练习:找出你的编译器的 algorithm 相关档案,
  看看 STL find() 如何定义。
★仔细思考演算法泛型化过程中的思维模式,以及课中所给的几个良好编程风格,
如 pass by reference, 如 const 的运用
★学生来信问:
在听了您上的课後,觉得STL真是没话说。
不过,有些问题请教。
在intel系列的电脑,在记忆体的位置的位元组是倒过来放的(好像是Byte-reverse sequence吧)。
用久了之後,好像也不觉得有什麽差别。(底下为测试程式)
但这学期,修了PDA的Palm OS程式设计的专题,发现,在PDA上,记忆体的位置是没有倒过来放的。
这样一来,在从电脑传输至PDA时、在PDA上执行时,都要多花转换位元组的时间。
PDA不是比较晚出来吗(照理说,较新的较好不是吗)?为什麽会采这种放法呢?
是不是这样有什麽我所看不到的益处?
还有,底下这段程式,在BCB5.0下是没问题的。
但是在gcc(linux、breebsd)下,却有错误,也不像是不支援...
不知道是什麽缘故。希望候sir不吝赐教
以下为gcc下的错误显示。
[root@linux realDK]# g++ test.cpp
test.cpp: In function `void print(T &)':
test.cpp:37: parse error before `;'
test.cpp: In function `void print<vector<char,__default_alloc_template<true,0> >
>(class vector<char,__default_alloc_template<true,0> > &)':
test.cpp:28:   instantiated from here
test.cpp:38: `i' undeclared (first use this function)
test.cpp:38: (Each undeclared identifier is reported only once
test.cpp:38: for each function it appears in.)
test.cpp:38: confused by earlier errors, bailing out
#pragma hdrstop
#include <iostream>
#include  <vector>
#include <cstdio>
using namespace std;
template <typename T>
void print(T &pricon);
static void convert (unsigned long d)
{
unsigned char *p;
unsigned char b1,b2,b3,b4;
        vector<char> s1;
p=(unsigned char *)(&d);
b1=*p;
p++;
b2=*p;
p++;
b3=*p;
p++;
b4=*p;
        s1.push_back(b1);
        s1.push_back(b2);
        s1.push_back(b3);
        s1.push_back(b4);
        print(s1);
}
template <typename T>
void print(T &pri_con)
{
        if (pri_con.empty())
        cout<<"container is empty!\n";
        else{
                T::iterator i;
                for (i=pri_con.begin();i!=pri_con.end();i++){
                printf("%x",*i); //cout<<(int)*i<<"  ";
                }
        }
}
int main(int argc, char* argv[])
{
        int it=0x12345678;
        printf("before convert:%x",it);//cout<<"before convert:";
        cout<<endl<<"after convert:";
    convert(0x12345678);
}
//---------------------------------------------------------------------------
侯捷回覆:
记忆体位元组的排列方式有 big-endian(正放)和 little-endian(倒放)两种,
无关好坏。
你这程式的模式切割很不理想,UI介面部份和核心运算部份一定要切割乾净。
修改如下。课堂上解说。
#pragma hdrstop        // for what ?
#include <iostream>
#include <vector>
#include <algorithm>   // for_each()
using namespace std;
void convert (const unsigned long& d, vector<char>& v)
{
  unsigned char* p = (unsigned char*)(&d);
  v.push_back(*(p++));
  v.push_back(*(p++));
  v.push_back(*(p++));
  v.push_back(*(p++));
}
void print(const char& elem)
{
   cout << hex << (int)elem << dec;
}
int main(int argc, char* argv[])
{
    int it=0x12345678;
    cout << "before convert: " << hex << it << dec;
    vector<char> v;
    convert(it, v);
    cout << endl << "after convert:";
    for_each(v.begin(), v.end(), print);
}
//-- the end

■第4周:03/22
运算子多载化 operator overloading.

C++ Primer 3/e, chap15.
ref. [EC] item11,15,16,17
ref. [MEC] item6,7
ref. STL <memory> class auto_ptr;

课堂示范: operator++, operator++(int), operator<< .

思考:prefix 和 postfix 的效率比较
●程式例1
#include <iostream>
using namespace std;
class INT
{
friend ostream& operator<<(ostream& os, const INT& i);
public:
  INT(int i) : m_i(i) { };
  // prefix : increment and then fetch
  INT& operator++()
  {
    ++(this->m_i);
    return *this;
  }
  // postfix : fetch and then increment
  const INT operator++(int)
  {
    INT temp = *this;
    ++(*this);
    return temp;
  }
private:
  int m_i;
};
ostream& operator<<(ostream& os, const INT& i)
{
  os << '[' << i.m_i << ']';
  return os;
}
int main()
{
  INT I(5);
  cout << I++;  // [5]
  cout << ++I;  // [7]
}


★练习:operator--, operator--(int),写不下去就看上述范例,再继续写。
动手写一遍比没动手的学习效果好许多。打铁要趁热。
★练习:找出 STL 中的 auto_ptr,看看其中的 operator*(), operator->()
找出STL中的 for_each()演算法,看看其中如何运用 opeartor().

■第5周:03/29
运算子多载化 operator overloading.
仿函式 functor
function template

C++ Primer 3/e, chap15.
ref. [EC] item11,15,16,17
ref. [MEC] item6,7
ref. STL <memory> class auto_ptr;

课堂示范: operator(), operator=, operator*, operator->.

思考:cout << i++ << i--;
提示:function parameters 的 evaluation 次序

同学提问:延续第3周的 ambiguous 问题。以下是我的测试与回答。
#include <iostream>
#include <list>
using namespace std;
/*
以下如果写的是:
I find(I begin, I end, T value)   // (1)
GCC2.9[o], VC6[o], cb4[x]
CB4 会给错误讯息:ambiguity between std::find and find
但我并没有 #include <algorithm> 为何会决议至 std::find 呢?
猜测:可能 BCB4 某处有 std::find 的宣告,并且被本程式含入。
以下如果写的是:
I find(I begin, I end, const T value)  // (2)
GCC2.9[o], VC6[o], cb4[x]
CB4 会给错误讯息:ambiguity between std::find and find
但我并没有 #include <algorithm> 为何会决议至 std::find 呢?
猜测:可能 BCB4 某处有 std::find 的宣告,并且被本程式含入。
以下如果写的是:
I find(I begin, I end, T& value)      // (3)
GCC2.9[w], VC6[o], cb4[o]
GCC2.9 会给警告讯息:
  initialization of non-const reference `int &' from rvalue `int'
在 GCC2.9 中:
(1)(4) 可共存。决议结果为 (4).
(2)(4) 可共存,决议结果为 (4).
(3)(4) 可共存,不再有警告,因为决议结果为 (4).
(2)(3)(4) 可共存,决议结果为 (4).
(1)(3)(4) 不可共存, redefined!
(1)(2)(3)(4) 是 function template overloading。
本例由於函式名称相同,函式叁数个数也相同,
所以(1)(2)(3)(4)也就是 function template specialization 的关系。
见 C++ Primer 3/e p.522
*/
template <typename I, typename T>
I find(I begin, I end, const T& value)   // (4)
{
  cout << "jjhou(4)" << ' ';
  while (begin != end && *begin != value)
     ++begin;
  return begin;
}
int main()
{
  int ia[5] = { 0,1,2,3,4};
  int* pi = find(ia, ia+5, 2);
  cout << *pi << endl;       // jjhou(4) 2
  double da[5] = { 0,1,2,3,4};
  double* pd = find(da, da+5, 2.0);
  cout << *pd << endl;       // jjhou(4) 2
  list<int> mylist(ia, ia+5);
  list<int>::iterator ite;
  ite = find(mylist.begin(), mylist.end(), 4);
  cout << *ite << endl;      // jjhou(4) 4
}

同学提问:以下标准的 iterator 写法,
(A)和 (B)会不会唤起 MyIter<T>::operator*  ?
(C)会不会唤起 MyIter<T>::operator++()  ?
template <typename T>
struct MyIter
{
  T* ptr;
  T& operator*()  const { return *ptr; }
  T* operator->() const { return  ptr; }
  MyIter& operator++()
    {
      /*...*/               // depend on what need to do...
      return *this;         // (A)
    }
  const MyIter operator++(int)
    {
      MyIter tmp = *this;   // (B)
      ++*this;              // (C)
      return tmp;
    }
  // ...
};
侯捷回覆:
(A)和 (B)不会唤起 MyIter<T>::operator*  
(C)会唤起 MyIter<T>::operator++() 
为什麽一个会而一个不会?思考一下型别。

●程式例1
// C++ Standard:Adaptable Binary Function must inherit from binary_function.
template <class Arg1, class Arg2, class Result>
struct binary_function {
    typedef Arg1 first_argument_type;
    typedef Arg2 second_argument_type;
    typedef Result result_type;
};      
template <class T>
struct less : public binary_function<T, T, bool> {
    bool operator()(const T& x, const T& y) const { return x < y; }
};
template <class InputIterator, class Predicate>
typename iterator_traits<InputIterator>::difference_type
count_if(InputIterator first, InputIterator last, Predicate pred) {
  typename iterator_traits<InputIterator>::difference_type n = 0;
  for ( ; first != last; ++first)
    if (pred(*first))
      ++n;
  return n;
}
//using:
vector<int> iv(...);
count_if(v.begin(), v.end(), bind2nd(less<int>(),40));

●程式例2
// 本例测试 function object(functor), function template,
// function pointer 三者搭配 STL algorithms for_each() 的情况
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
// (1) function object (functor)
template <typename T>
class print1
{
public:
  void operator()(const T& elem)
     {  cout << elem << ' ';  }
};
// (2) function template
template <typename T>
void print2(T elem)
{
  cout << elem << ' ';
}
// (3) regular function
void print3(int elem)
{
  cout << elem << ' ';
}
int main()
{
  int ia[6] = { 0,1,2,3,4,5 };
  vector< int > iv(ia,ia+6);
  // (1)
  // 以下,print1<int>() 是个暂时物件
  for_each(iv.begin(), iv.end(), print1<int>());  // 0 1 2 3 4 5
  // (2)
  // 以下,需先以 pfi 将 print2 具现化。由於 function template
  // 的具现行为属於隐式行为(在取址或呼叫时发生),所以我们无法以
  // print2<int> 直接做为 for_each() 的第三叁数。
  void (*pfi)(int) = print2;
  for_each(iv.begin(), iv.end(), pfi);            // 0 1 2 3 4 5
  // (3)
  // 将函式名称(函式指标)当做 function object 来用。
  for_each(iv.begin(), iv.end(), print3);         // 0 1 2 3 4 5
}
同学对上例的疑问:C++ Primer 3/e p505 不是可以明白指定 template 引数吗?
换句话说可以写这样的型式:print2<int>(5); 那为什麽上述的 (2) 不能写成:
     for_each(iv.begin(), iv.end(), print2<int>);
回覆:p.505 是explicit template arguments,不是 explicit instantiation.  
p.512 有 explicit instantiation declarations,但各家支援程度不一,效果如何也不知。
上例若改为 for_each(iv.begin(), iv.end(), print2<int>); 可通过 CB4,却通不过 VC6
和 GCC2.91。

习题:利用春假时间,写一个 list(叁考 C++ Primer 5.11),
并针对该 list 设计其 iterator。再写一个测试程式。

■第6周:04/05 春假
■第7周:04/12

C++ Primer 3/e, chap16 : class templates

specialization, partial specialization.
partial specialization非常重要,千万不要缺课。否则你的 GP 功力将失大半。

今天可能会提到一点 iterator 和 iterator traits 的概念,尤其重要。
同学提问:为什麽使用 VC++ <iostream>,会和 friend 产生怪异的结果 
侯捷回覆:课程一开始我就请同学上网看「C++ 标准与实作之间」这篇文章,
          为什麽到现在还有人询问这种问题。我很失望。
同学提问:C++ Primer p699 的 default ctor 不甚了解。
侯捷回覆:再叁考 Effective C++ item45: 
          Know what functions C++ silently writes and calls
同学们不要奇怪老师为什麽常常要你们看这本书,看那本书,看不完的书。
学精一样东西,一本书是不够的。好书就只是那几本,做为一个学习者,
你必须齐备那些好书。老师把那些书译出来,对各位而言,应该是很幸福
的事了。学生没钱买书,这不在老师的解题范围内。

我会在课程上对 default ctor 再做一次解释。
同学提问:在看到您网站上的"芝麻开门 从 Iterator 谈起"中的 auto_ptr
有些疑问想请教:
(1) auto_ptr 中的 operator= 用到了 rhs.release() 及reset(), 其中
release() 及reset 的作用为何?
(2) 对於 auto_ptr 这个例子,其中某些函式的省略原则为何?
    希望能学起来,因为可以训练表达能力

侯捷回覆:(1) 这种问题查书可得答案,例如 The C++ Standard Library.
(2) 没有可以省略的。我们前几堂课曾经以 auto_ptr 为蓝本,以其中一小部份
完成一个 smart pointer,其他部份舍弃,那是因为其他部份对我们当时的目的
而言没有必要。但它们对 auto_ptr 是不可省略的。

同学提问:我在这里依照老师的意思写了一个 iterator, 叫做 list_ite
但我们常见一种宣告方式  list<int>::iterator ite;
这两种有何差别??是因为後者是被定义在"容器"内吗??

侯捷回覆:是的。如果你要 iterator 定义於一个容器之内(事实上应该如此),
今天就会讲到。主要是利用 nested typedef 做一点小改装。


同学该思考期末作业的解法了。
■第8周:04/19 期中考(本课程无期中考。本周停课)
传送日期: 2001年4月21日 AM 09:32
主旨: 你怎麽舍得睡觉?
侯老师,我是个工程师,从台北慕名前来听您的GP,看到您上课求解的态度,
循循诱导学生思索问题的方向,说真的,听有关程式语言理论与实务的课,
从来不曾如此享受过,您也一再身体力行,show出复杂技术的本质面,
偶而点出这些不凡技术背後的平凡动机,还谆谆地告诉我们该去看哪本哪本书,
这岂是一句感动了得!您把伟大的东西变平凡了,我想,大师风范,当如是也。

那天,正在听您替一位同学解惑,也正"享受"时,忽而从我左後及右後座位,
间或传来『好困』『好想睡觉』,於是,一时兴起,举手发言,想用此举,
看能不能使那几位同学振作精神,而老师果然是大师气度,还两次说谢,
真令我自惭。我听说,诺贝尔得主,不约而同都很谦虚,我觉得有可能。

就业以後,再进修的成本大增许多,挑书,挑老师,由於时间预算有限,
变得十分紧缩。元智大学有眼光,侯老师更是慷慨大方,倾囊相授,许多同学
应该也慕名,或者知道「侯sir」在这领域的地位,前来修课,不至於迷迷糊糊
开错房间闯进来,睡觉,罢。

十几年前,我有没有睡觉,错过重要技术呢?一定有。 

侯捷回覆:偶而收到这样的来信,便是我「不远千里」去上课的最大动机。
同学们不知道,上一堂课3小时,我得 4:30pm 从新竹出发,10:30pm 才回到家。
你从台北来听课,也是够辛苦的了。

什麽样的学生都有。教书这麽多年了,我习以为常。但凡课堂上有一两位努力用功
的学生,我便感到欣慰。

本学期努力用功的学生还算不少。

> 十几年前,我有没有睡觉,错过重要技术呢?一定有。

我也有 :) 人嘛,总是一代一代犯下同样的错误。看看历史书,古今对照
一下便知。有时我看着学生,看着他们的天真斑烂,看着他们的热情激昂,
看着他们的愚蠢幼稚,想着,如果是我的子弟,有我引导他们,真不知对他们是
多大的幸福。我的大学生涯,也没有什麽父兄师长开拓我的视野,教诲我的行止
(也许是因为我的愚蠢幼稚,没去亲近值得亲近的师长),因而对此特别感怀。

■第9周:04/26
C++ Primer 3/e, chap12,iterator 相关部份

侯捷 STL系列文章第二篇:泛型指标与 Traits 技术
《程序员》杂志 2001/03 芝麻开门, 从Iterator谈起
补充:type_traits
traits 是 GP/STL 的敲门砖。没有这项技术能力,你很难深入 GP/STL 的世界。
缺了这门课,同学,你将遗憾终身。

课堂上同学提了一个问题,令我愣了一下,一时回答不出来。这个问题是:

Q: STL 正规作法是对 iterator_traits 做 partial specialization,例:
template<I>
struct iterator_traits { ... };
template<T>
struct iterator_traits<T*> {... };

但如果我们在 iterator 层面就做 partial specialization,例:

template<T>
class Iter { ... };
template<T>
struct Iter<T*> {... };

有何利弊得失?
A: 根本不对头。iterator 的行为在於模仿指标,其template 型别叁数应该是其
所指之物的型别,不应该又是一个指标。将 iterator 特化,是违背了
iterator 的设计理念。

以下是 SGI STL 的 iterator_traits 源码节录,并加上我自己的一些测试。测试并不全面,你可以加上你自己的想法。这份源码也就是《泛型程式设计与STL》p42 的程式码整理。至於 type_traits 的作法,请叁考 SGI STL <type_traits.h>。

// VC6 [x], BCB4 [o], G++ [o]  (VC6 not support partial specialization)
#include <iostream>
#include <cstddef>  // for ptrdiff_t
// 注意,不能开放 namespace std; 否则会和 std 的 iterator_trait 冲突
struct input_iterator_tag { };
struct output_iterator_tag { };
struct forward_iterator_tag : public input_iterator_tag { };
struct bidirectional_iterator_tag : public forward_iterator_tag { };
struct random_access_iterator_tag : public bidirectional_iterator_tag { };
template <class Iterator>
struct iterator_traits {
  typedef typename Iterator::iterator_category  iterator_category;
  typedef typename Iterator::value_type         value_type;
  typedef typename Iterator::difference_type    difference_type;
  typedef typename Iterator::pointer            pointer;
  typedef typename Iterator::reference          reference;
};
// partial specialization for regular pointers
template <class T>
struct iterator_traits<T*> {
  typedef random_access_iterator_tag  iterator_category;
  typedef T                           value_type;
  typedef ptrdiff_t                   difference_type;
  typedef T*                          pointer;
  typedef T&                          reference;
};
// partial specialization for regular const pointers
template <class T>
struct iterator_traits<const T*> {
  typedef random_access_iterator_tag  iterator_category;
  typedef T                           value_type;
  typedef ptrdiff_t                   difference_type;
  typedef const T*                    pointer;
  typedef const T&                    reference;
};
template <class Category,
          class Value,
          class Distance = ptrdiff_t,
          class Pointer = Value*,
          class Reference = Value&>
struct iterator {
  typedef Category  iterator_category;
  typedef Value     iterator_value;
  typedef Distance  iterator_distance;
  typedef Pointer   iterator_pointer;
  typedef Reference iterator_reference;
};
template <class InputIterator>
typename iterator_traits<InputIterator>::value_type
sum_nonempty(InputIterator first, InputIterator last)
{
  typename iterator_traits<InputIterator>::value_type  result = *first++;
  for (; first != last; ++first)
    result += *first;
  return result;
}
template< class InputIterator, class T >
typename iterator_traits<InputIterator>::difference_type
count( InputIterator first, InputIterator last, const T& x )
{
   typename iterator_traits<InputIterator>::difference_type n = 0;
   for ( ;  first != last; ++first)
     if (*first == x)
       ++n;
   return n;
}
template <class InputIterator, class Distance>
void advance(InputIterator& i, Distance n, input_iterator_tag)
{
  for ( ; n > 0; --n, ++i );
}
template <class BidirectionalIterator, class Distance>
void advance(BidirectionalIterator& i, Distance n, forward_iterator_tag)
{
  advance(i, n, input_iterator_tag());
}
template <class RandomAccessIterator, class Distance>
void advance(RandomAccessIterator& i, Distance n, random_access_iterator_tag)
{
  i += n;
}
template <class BidiectionalIterator, class Distance>
void advance(BidiectionalIterator& i, Distance n, bidirectional_iterator_tag)
{
  if (i >= 0)
      for ( ; n > 0; --n, ++i ) { }
  else
      for ( ; n < 0; ++n, --i ) { }
}
// top level
template <class Iterator, class Distance>
inline void advance(Iterator& i, Distance n)
{
  advance(i, n, iterator_traits<Iterator>::iterator_category());
}
template <class Category,
          class Value,
          class Distance = ptrdiff_t,
          class Pointer = Value*,
          class Reference = Value&>
class MyIter : public iterator<Category, Value, Distance, Pointer, Reference>
{
};
void main()
{
  int ia[5] = {0, 1, 2, 3, 4};
  int total = sum_nonempty(ia, ia+5);
  int c = count(ia, ia+5, 2);
  std::cout << total << std::endl;  // 10
  std::cout << c << std::endl;      // 1
  std::cout << *ia << std::endl;    // 0
  int* pi = &(ia[0]);
  advance(pi, 3);
  std::cout << *pi << std::endl;    // 3
  MyIter<random_access_iterator_tag, int> myiter;
}

■第10周:05/03
再论 iterators:insert iterators, stream iterators.
MSDN 2001/04: An STL Iterator for the Internet Explorer Cache, 
by Samir Bajaj
能解以下题目,学期成绩 95 分以上:
写一个 file_iterator,以及三个 functors,使以下行为能够成功:
int main()
{
  // count of file entries in the directory
  cout << "Total count: " <<
          count_if(file_iterator(), file_iterator(0),
                   always_true<file_iterator::value_type>())
       << endl;

  // for_each() to display all entries
  for_each(file_iterator(), file_iterator(0), display_file_entry());

  // accumulate() to add up the size of all file entries
  cout << "Total: ";
  cout << accumulate(file_iterator(), file_iterator(0), 0,
                     file_size())
       << "bytes." << endl;

  return 0;
}
执行结果:例如,在 Windows DOS-box 中执行如下:
c:\> mydir <enter>
Total count: 3
...(filename)
...(filename)
...(filename)
Total: 102487 bytes.
提示:
1. 运用 Win32 APIs : FindFirstFile(), FindNextFile(),
    GetCurrentDirectory().
2. file_iterator 应该对 operator++, operator*, operator==, 
   operator!= 加以多载化。
3. 叁考前面所说的文章 MSDN 2001/04,An STL Iterator for the 
   Internet Explorer Cache, by Samir Bajaj
4. 叁考 STL istream_iterator, ostream_iterator 的源码。


同学来信:透过老师亲手为我们,演绎一遍iterator_traits的技术由来与实作,
相信许多同学跟我一样感觉,被隔空打通任督二脉,内功又增一旬,
过瘾至极!

■第11周:05/10
functor, adaptor 源码剖析, allocator 剖析
同学来信:昨晚,又见老师展现魅力,亲手为我们重建functor adaptor的
发明过程,精彩绝伦,BRAVO!在这麽短的时间,可以从原始动机,
一路演化,到一个solid 版本的诞生,呼,我差点喊 ENCORE!


同学来信:老师这星期的上课内容真的很棒很棒!! 我敢说昨晚的收获绝不是
在短时间内自读所能达到!! 不过老师却只花了1.2个小时的时间就
让我们有如沐春风的感受, 实在是相当的感谢老师!!
害我那天晚上不顾隔天的考试, 赶快将老师所教授的内容从头到尾
重做一次, 深怕过了那天晚上就忘记了该堂课所收获的点点滴滴.
ㄟ....希望考试成绩不会受太大的影响....^_^""

有一位同学,每周四从台北下班後直奔元智,旁听 GP 课程。通常到达之後只剩最後一个小时可以听。一方面我佩服他的毅力,一方面我对他的遗漏感到惋惜。但他总是说能够听这一小时,也很满足。

这一周我从 count_if() 的第三个叁数谈起,谈到一般的 functor, 谈到 adaptable functor,谈到 functor adaptor。这个「问题的发生与解决,以及进一步的弹性扩大」的流程与演绎,是各位绝不可能在任何一本书上看到的。这位远道而来的同学没有能够听到这一段,我非常为他惋惜。为此,我把这个流程整理放在网上。

以下是仿函式和配接器的标准用法

// vc6[o] cb4[o] gcc[o]
#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>
using namespace std;
int main()
{
  int ia[10] = { 0,1,2,3,4,5,6,7,8,9 };
  vector<int> iv(ia, ia+10);
  // 以下计算满足 "less than 5" 的元素个数。采用 STL components
  cout << count_if(iv.begin(), iv.end(), bind2nd(less<int>(),5));
}
以下分析问题的由来、解决、和扩充。
问题分析:如果不采用bind2nd(less<int>(),5),而是直接使用一般的 
functor,如下可行:

// vc6[o] cb4[o] gcc[o]
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 以下这个仿函式,用来实作出「小於5」的条件判断式(predicate)
template <class T>
class less_than_5
{
public:
  bool operator()(const T& n) const
  { return n < (T)5; }    // 相当於 operator<(n, (T)5);
};
int main()
{
  int ia[10] = { 0,1,2,3,4,5,6,7,8,9 };
  vector<int> iv(ia, ia+10);
  // 以下计算满足 "less than 5" 的元素个数
  cout << count_if(iv.begin(), iv.end(), less_than_5<int>());
}
为了令上一例的 less_than_5<T> 更具弹性,我们可以设计一个 functor,
使它接受某个动作 op(取代前例的 operator<)和某个数值 n(取代前例的 5),
并令 n 为 op 的第二引数。为此,我们将此 functor 命名为 Jbinder2nd
(前面加 J 以便与 STL component 区分)。为了接受动作op 和数值 n,
必须有两个data members用来储存它们:

template <class OP, class T>
class Jbinder2nd
{
protected:
  OP _op;
  T _n;
...
};
但由於 n 既然要做为 OP 的第二引数,其型别 T 和 OP 之间便有一种相依性,
所以我们可以舍弃 T,以 OP::second_argument_type 取而代之(前提是 OP 为
可配接的,adaptable)。由於 OP 未定,所以我们必须加上关键字 typename 以
协助编译器判断 OP::second_argument_type 是啥东西:

template <class OP>
class Jbinder2nd
{
protected:
  OP _op;
  typename OP::second_argument_type _n;
为了让 Jbinder2nd 接受动作 op 和数值 n,我们可以设计在其 ctor 中接受之。
於是变成:
template <class OP>
class Jbinder2nd
{
protected:
  OP _op;
  typename OP::second_argument_type _n;
public:
  Jbinder2nd(const OP& op, const typename OP::second_argument_type& n)
    : _op(op), _n(n) { }
以下是完整内容

// vc6[o] cb4[o] gcc[x]
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;
template <class OP>
class Jbinder2nd
{
protected:
  OP _op;
  typename OP::second_argument_type _n;
public:
  Jbinder2nd(const OP& op, const typename OP::second_argument_type& n)
    : _op(op), _n(n) { }
  typename OP::result_type		// 传回值型别
  operator()(const typename OP::first_argument_type& x) const
  { return _op(x, _n); }
};
int main()
{
  int ia[10] = { 0,1,2,3,4,5,6,7,8,9 };
  vector<int> iv(ia, ia+10);
  // 先产生一个 Jbinder2nd object(这是个 predicate)
  // 这个动作对於一般程式员有点困难。
  Jbinder2nd< less<int> > jb(less<int>(),
                             (less<int>::second_argument_type)5); // GCC error
  // 然後喂给 count_if() 做为第三叁数
  cout << count_if(iv.begin(), iv.end(), jb);
}
由於「直接产生 Jbinder2nd object」(如前例)在语法上有点难度,
我们可以利用function template 的「引数自动推导」能力,帮我们
简化工作。加上这一中介层,使客端所需面对的难度降低了不少。同时,
为了让 Jbinder2nd<OP> 也具备adaptable 能力,最好是令它继承自 
std::unary_function。

// vc6[o] cb4[o] gcc[o]
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;
// class template
template <class OP>
class Jbinder2nd 
  : public unary_function<typename OP::first_argument_type,
                          typename OP::result_type> {
{
protected:
  OP _op;
  typename OP::second_argument_type _n;
public:
  Jbinder2nd(const OP& op, const typename OP::second_argument_type& n)
    : _op(op), _n(n) { }
  typename OP::result_type
  operator()(const typename OP::first_argument_type& x) const
  { return _op(x, _n); }
};
template <class OP, class T>
inline
Jbinder2nd<OP>    // 传回值型别
Jbind2nd(const OP& op, const T& n) 	// 函式名称与叁数列
{
  // 以下,型别过长,另取一个简短别名
  typedef typename OP::second_argument_type OpSat;  
  return Jbinder2nd<OP>(op, (OpSat)n);
  // 以上产生一个 Jbinder2nd object。
}
int main()
{
  int ia[10] = { 0,1,2,3,4,5,6,7,8,9 };
  vector<int> iv(ia, ia+10);
  cout << count_if(iv.begin(), iv.end(), Jbind2nd(less<int>(),5));
}

请拿这份历经一步一步阶段演化而来的代码,与第一例(使用标准的STL组件)
比较,并取这份代码中的 Jbinder2nd<OP> 和 Jbind2nd(),与 STL 的 
binder2nd<OP> 和 bind2nd() 比较,你会发现它们完全一样。
换句话说,你现在知道 binder2nd<OP> 和 bind2nd() 是怎麽设计出来的了。

■第12周:05/17
deque 剖析
■第13周:05/24
set/map 剖析 (兼谈 Red-Black Tree), 
hash table 剖析
set algorithms 剖析
  
传送日期: 2001年5月25日 PM 08:13
主旨: 感激!!
侯老师,您好:
        虽然您获得来自各方的答谢已频频不断,但我还是以心存万分感激的心情,
谢谢您允许学生旁听您的课程.自从在网路上得知老师要在元智大学於晚间 教
授"Generic Programming and STL"这门课时,我因为信奉佛教,顿时认为是平日念
佛方能获得如此的因缘.此外, 课前曾多次电话联系学校确定开课时间与教室
号码并以现地勘查方式到该校一游,深怕错失机会.
       在此,还是要再次向您道歉,因为从第一天上课至课程结束都未曾向您报告;
在最後为了聊表心意,送您的咖啡豆,还不知是否合您的口味,亦或又为您增加处
理的困扰.
       此次课程不仅帮我节省了最宝贵的学习时间并能达到事半功倍的效果;而且
能现场聆听一位在此领域研究如此透彻的老师所教授的课程真是令人兴奋.课堂
里得知您作学问的精神与问题思考的方式,更有助於我们日後引为作学问的借镜.
想想那些能在大学时代就接受您的薰陶的学生,在佛家来说是上辈子修来的.我虽
然不像他们,但是有此机会,我以为比起其他许多想来上课,但因为种种因素而无法
如愿的人又幸运多了.
      最後,再次谢谢您的教导.
 
    敬祝        身体健康   
                    万事如意
  
侯捷回覆:相逢自是有缘。这是我们的缘份。您的咖啡让我很感动,很开心。
                                                              
 
■第14周:05/31  停课

■第15周:06/07  期末作业检讨.1
以下各组於本周课堂上交出作业及报告。上台次序如同组别次序。
每组需提供书面报告一份,原始码磁片一份给老师,并推派一位(或多位)同学
上台主讲。原始码当场编译(请使用 command line 模式),机器由老师准备。
讲述内容请自己构思(你认为什麽是该讲的呢?你认为听众想听什麽呢?)
提示一点:书面报告对於你的成绩,影响很大。
课堂上未能呈交作业及报告,或是当天缺席者,作业成绩以0分计。
2001/06/29 补注:名单之後为学期总成绩。我曾说过,选择 file-iterator 题目者,
成绩在 95 分以上。这是个口误,我真正的意思是,总成绩中的「程式表现」部份
将获得 95% 的分数。总成绩包括 (1)口头报告、(2)书面报告、(3)程式表现、
(4) 平时表现 四项。
组别 (0)
864029	王诏凯(未按时间编列组别,总成绩扣5分)67
874003	蔡远铭(未按时间编列组别,总成绩扣5分)67

组别 (1)
874021    周岳桦(未按时间编列组别,总成绩扣3分)79
874023    黄思伟(未按时间编列组别,总成绩扣3分)79
874026    朱仕任(未按时间编列组别,总成绩扣3分)79
874055    张贤宗(未按时间编列组别,总成绩扣3分)79

组别 (2)
874067 王再发 71
874095 林奕璋 71
874115 卢威杉 71
874117 朱义胜 71

组别 (3)
孟宪君 874073   82
郑博文 874074   82
刘智强 874121   82
郑人豪 874125   82
傅重嘉 874113(未按时间编列组别,总成绩扣3分) 79
赵元鳌 874081(未按时间编列组别,总成绩扣5分) 77

组别 (4)
874011     林      69
874013     林学谦  69
874053     黄信    71

组别 (5)
882355	蔡秉霖 85
882357	吴俊德 85
882333	李文智 85
882207  施威年 85

组别 (6)
874006 周树伟 88
874008 王彦凯 88
874017 陈??   88
874060 邱致玮 88
874063 徐之豪 88

■第16周:06/14  期末作业检讨.2
以下各组於本周课堂上交出作业及报告。上台次序如同组别次序。
每组需提供书面报告一份,原始码磁片一份给老师,并推派一位(或多位)同学
上台主讲。原始码当场编译(请使用 command line 模式),机器由老师准备。
讲述内容请自己构思(你认为什麽是该讲的呢?你认为听众想听什麽呢?)
提示一点:书面报告对於你的成绩,影响很大。
课堂上未能呈交作业及报告,或是当天缺席者,作业成绩以0分计。
2001/06/29 补注:名单之後为学期总成绩。我曾说过,选择 file-iterator 题目者,
成绩在 95 分以上。这是个口误,我真正的意思是,总成绩中的「程式表现」部份
将获得 95% 的分数。总成绩包括 (1)口头报告、(2)书面报告、(3)程式表现、
(4) 平时表现 四项。
组别 (7)
874075  梁怀中 75
874082  杨士贤 75
874089  陈世光 75
874106  简银谷 75

组别 (8)
874002    王志   83
874004    李凯勋 83
874016    黄正和 83
874052    吴炳宜 83
874057    吴庆鑫 83
874061    刘志孝 83

组别 (9)
882204 刘新玫 80
882218 林康司 80
882219 邱世雄 80
882250 洪一菁 80

组别 (10)
864131 罗冲聘 90
865121 廖志彬 90
864138 史永建 95
864127 詹儒忠 90
864105 郭裔铭 90

组别 (11)
874076 徐庆豪 82
874077 赖宜辰 82
874097 李升家 82
874093 施志翰 82
874103 吴承熹 82
874112 郑欣智 82
874116 黄琨富 82

组别 (12)
882234    赵品权 92
882203    陈纯怡 92
882224    陈韵如 92
882227    刘本杰 92
882235    廖茂育 92
882236    李光曜 95
组别 (13)
874019  林韦肜 94
874020  谢彦伟 99
874027  李建勋 94
874032  彭彦璁 94
874058  李德升 94
■第18周:06/21  期末考 停课