## CSDN博客

### Generic<Programming>: 再谈Min和Max

Andrei Alexandrescu
ye_feng译　本文代码

##### Min和Max

#define min(a, b) ((a) < (b) ? (a) : (b))


template <class T>
const T& min(const T& lhs, const T& rhs)
{
return lhs < rhs ? lhs : rhs;
}


double a, b;
...
min(a, b) += 2;


template <class T>
T& min(T& lhs, T& rhs)
{
return lhs < rhs ? lhs : rhs;
}


int a;
short int b;
...
int smallest = min(a, b); // error: can't figure out T
// in template instantiation


int smallest = min(a, int(b)); // aha, T is int


##### 一个（几乎是）好的开始

template <class L, class R>
class MinResult {
L& lhs_;
R& rhs_;
public:
operator L&() { return lhs_ < rhs_ ? lhs_ : rhs_; }
operator R&() { return lhs_ < rhs_ ? lhs_ : rhs_; }
MinResult(L& lhs, R& rhs) : lhs_(lhs), rhs_(rhs) {}
};

template <class LR>
class MinResult<LR, LR> {
LR& lhs_;
LR& rhs_;
public:
operator LR() { return lhs_ < rhs_ ? lhs_ : rhs_; }
MinResult(LR& lhs, LR& rhs) : lhs_(lhs), rhs_(rhs) {}
};

template <class L, class R>
MinResult min(L lhs, R rhs)
{
return MinResult(lhs, rhs);
}


int a, b;
...
min(a, b);


int c = min(a, b);


int a;
short int b;
extern Fun(int);
extern Fun(short int);
...
Fun(min(a, b)); // error! Don't know which overload to invoke!

MaxResult<int, short int>支持两种转换：转成int&和转成short int&。结果编译器无法在Fun的两个重载版本中决定选择哪一个。而基于宏的方法再一次通过了测试，如你所期望的那样，最终会调用Fun(int)。
##### 寻找一个类型

template <class L, class R>
MINTYPE(L, R)
Min(L& lhs, R& rhs)
{ return lhs < rhs ? lhs : rhs; }

template <class L, class R>
MINTYPE(const L, R)
Min(const L& lhs, R& rhs)
{ return lhs < rhs ? lhs : rhs; }

template <class L, class R>
MINTYPE(L, const R)
Min(L& lhs, const R& rhs)
{ return lhs < rhs ? lhs : rhs; }

template <class L, class R>
MINTYPE(const L, const R)
Min(const L& lhs, const R& rhs)
{ return lhs < rhs ? lhs : rhs; }


#define MINTYPE(L, R) typename MinTraits<L, R>::Result

template <class L, class R> struct MinTraits;

// Specialization for the L == R case
template <class LR> struct MinTraits<LR, LR> {
typedef LR& Result;
};

// Specialization for bool and char
template <> struct MinTraits<bool, char> {
typedef char Result;
};

...


class Shape {
...
unsigned int Area() = 0;
};

bool operator<(const Shape& lhs, const Shape& rhs) {
return lhs.Area() < rhs.Area();
}

class Rectangle : public Shape { ... };

void Hatch(Shape& shape)
{
Rectangle frame;
...
Shape& smallest = Min(shape, frame);
... use smallest ...
}


shape < frame ? shape : frame


shape < frame ? shape : Shape(frame)


##### Loki

Okey，刚才我撒谎了。代码只有80行，但这没有把使用的类库计算在内。更确切地说，我用了Loki，在我写的书[5]里介绍的一个通用库。在众多功能中，Loki提供了高级的类型操纵手段。Min实现里用到的Loki工具有：

1. Typelists。除了保存的是类型而不是值外，Typelists[6]和通常的列表一样。例如下面语句：

typedef TYPELIST_3(float, double, long double) FloatingPointTypes;


Loki::TL::IndexOf<FloatingPointTypes, double>::value


2. 我们要用到的第二个工具是Select类模板，它在[7]里有详细描述。简单来说，Select允许你根据一个编译时的布尔常量在两个类型中选择一个。例如：

typedef Loki::Select<sizeof(wchar_t) <
sizeof(short int), wchar_t, short int>::Result
SmallInt;


3. TypeTraits该类模板可以进行各种关于类型的推演，例如“这个类型是指针吗？它指向什么类型？”等等。我们只用到TypeTraits中的NonConstType类型定义。TypeTraits<T>::NonConstType是一个typedef，用来去掉T的const修饰，如果有的话。

4. 最后，我们会用到Conversion类[7]，它可以检测任意一个类型是否可以被隐式地转换为另一个类型。Conversion是实现上面提到的关于Shape和Rectangle的魔术的基础。

##### MinMaxTraits类模板

namespace Private
{
typedef TYPELIST_14(
const bool,
const char,
const signed char,
const unsigned char,
const wchar_t,
const short int,
const unsigned short int,
const int,
const unsigned int,
const long int,
const unsigned long int,
const float,
const double,
const long double)
ArithTypes;
}


先假设Result为R。

如果R可以被隐式地转换为L，那么把Result改成L。

如果L和R是算术类型，并且在上面的Private::ArithTypes里R排在L后面，那么把Result改为R。这一步用来处理所有算术转换。

如果L&可以被自动转换为R&，而不需要引入临时变量，那么把Result改为R&。这一步确保Min(frame, shape) 之类的调用返回Shape&。

如果R&可以被自动转换为L&，而不需要引入临时变量，那么把Result改为L&。这一步确保Min(shape,frame) 之类的调用返回Shape&。

##### Min和Max重载函数

Min和Max各有四个重载函数，对应于const和非const参数的四种组合。为了防止上面Shape/Rectangle例子里所讨论的对象切片（slicing）问题，Min的实现和经典的a < b ? a : b略有不同：

template <class L, class R>
typename MinMaxTraits<L, R>::Result
Min(L& lhs, R& rhs)
{ if (lhs < rhs) return lhs; return rhs; }

template <class L, class R>
typename MinMaxTraits<const L, R>::Result
Min(const L& lhs, R& rhs)
{ if (lhs < rhs) return lhs; return rhs; }

.. similar Max implementation ...


##### 分析

1. 提供函数调用的语义（包括类型检查），而不是宏的语义。Min显然可以做到。

2. 支持const和非const参数（包括在同一个调用里混用）。由于那四个重载函数，Min支持const和非const参数的任意组合。

3. 支持两个不同类型的参数（当然指有意义的情况）。Min的确支持不同类型的参数，而且还有很多宏和简单模板方法所达不到的智能：Min会分辨各种算术类型，并进行合理的转换。类型转换的选择过程（基于Private::ArithTypes）可以由库的作者控制。

4. 不需要显式实例化。Min不需要显式实例化。

Min对于指针也可以正确工作（甚至指向不同但有关联的类型的指针，如Shape*和Rectangle*）。这是由于算法中的第一步。

##### 参考书目

[1] http://www.aristeia.com/Papers/C++ReportColumns/jan95.pdf

[2] http://www.cuj.com/experts/1902/alexandr.htm

[3] http://www.gotw.ca/gotw/077.htm

[4] A. Alexandrescu. "Traits: the else-if-then of Types," C++ Report, April 2000.

[5] A. Alexandrescu. Modern C++ Design (Addison-Wesley Longman, 2001).

[6] J. Vlissides and A. Alexandrescu. "To Code or Not to Code," C++ Report, March 2000.

[7] A. Alexandrescu. "Generic: Mappings between Types and Values," C/C++ Users Journal Experts Forum, September 2000, http://www.cuj.com/experts/1810/alexandr.htm.

[8] R. Kurzweil. The Age of Spiritual Machines: When Computers Exceed Human Intelligence (Penguin USA, 2000).

0 0