14.2.1 下标索引操作符的交换性
C/C++(www.cppentry.com)里的指针之所以能够像数组一样被下标索引,与C/C++(www.cppentry.com)解释索引表达式的方式是分不开的。编译器将表达式ar[n]在编译期解释为*(ar + n)[Lind1994]。由于指针可以进行指针算术,所以p可以替代ar的位置从而变成*(p + n),这就是说,p[n]也是合法的。另外,还有一个有趣的现象[Dewh2003, Lind1994]就是内建的下标索引操作符的可交换性,不管是数组还是指针,它们的下标索引表达式都可以"反过来写",像这样:n[ar]和n[p]。有时认为[Lind1994]这除了可以用来糊弄新手或从"混乱C编码大赛(obfuscated C Code Contest)"胜出之外,其他一无是处。但是,它在作为C++(www.cppentry.com)最摩登的一个领域--泛型编程(www.cppentry.com)中确实可以带来一个实际的好处。事实上,就像[Dewh2003]中提及的,这种下标索引的可交换性只对内建的下标索引操作符有效,这一限制可以被用于约束一段代码只对数组/指针有效,而拒绝重载了下标索引操作符的类类型,具体做法像这样:
- template <typename T>
- void reject_subscript_operator(T const &t)
- {
- sizeof(t[0]); // 如果T不能进行下标索引的话,编译器就会在这里打住
- sizeof(0[t]); // 如果T只支持用户自定义的下标索引操作符,编译器也会在这里报错
- }
-
- void reject_subscript_operator(void const * const)
- {}
-
- void reject_subscript_operator(void * )
- {}
之所以提供针对void的重载版本,是因为对void (const)指针进行解引用是非法的。这些函数一起被用来阻止用户将自定义了下标索引操作符的类类型的实例作为实参传递。考虑程序清单14.1中的代码:
程序清单14.1
- struct Pointer
- {
- operator short *() const;
- };
-
- struct Subscript
- {
- int operator [](size_t offset) const;
- };
-
- void *pv = &pv;
- void const *pcv = pv;
- int ai[100];
- int *pi = ai;
- Pointer ptr;
- Subscript subscr;
-
- reject_subscript_operator(pv);
- reject_subscript_operator(pcv);
- reject_subscript_operator(ai);
- reject_subscript_operator(pi);
- reject_subscript_operator(ptr);
- reject_subscript_operator(subscr); // 这行代码编译出错!
你可能想知道为什么我们希望检测并禁止可下标索引的用户自定义类型。这是因为无论何时,能够发现可用于检测并强迫实施某些特性的方法总不是件坏事(见第12章)。泛型编程(www.cppentry.com)如今正在迅速发展,并且不会消退。回顾NUM_ELEMENTS()的定义,我们发现,如果将下标索引操作符颠倒一下,我们就可以阻止用户将它用于自定义的类型了,这种能力是先前的版本所没有的。- #define NUM_ELEMENTS(x) (sizeof((x)) / sizeof(0[(x)]))
-
- template <typename T>
- struct vect
- {
- T &operator [](size_t index);
- . . .
- };
-
- vect<int> vi;
- int ai[NUM_ELEMENTS(vi)]; // 采用新版NUM_ELEMENTS后,编译器对该行报错!