19.6 literal_cast
尽管在代码中使用字面整型量并不妥当,但我们在代码中使用命名常量仍然是相当常见的。有时候以不恰当的方式使用一个常量也是可能的,并且面临着因截断而丢失信息的危险。尽管大多数优秀的编译器对常量截断都会给出警告,但仍有部分编译器不会,或者至少在它们的常用配置下不会给出警告。况且,无论如何它也只不过是个警告而已。亲爱的读者,我知道你们当中任何人都不会让这个警告伴随着构建/发布(build/release)的过程,但是其他人并非都这么勤劳,或者有些人被迫要去抑制警告,或者从写得很糟糕的第三方库那里"继承"某种警告抑制机制。所以,如果有这么一个构造,它能够侦测出截断并强制产生一个编译期错误就好了。
函数模板literal_cast就是这么一个构造,如程序清单19.8所示:
程序清单19.8
- #ifdef ACMELIB_64BIT_INT_SUPPORT
- typedef int64_t literal_cast_int_t;
- #else /* ACMELIB_64BIT_INT_SUPPORT */
- typedef int32_t literal_cast_int_t;
- #endif /* ACMELIB_64BIT_INT_SUPPORT */
-
- template< typename T
- , literal_cast_int_t V
- >
- inline T literal_cast()
- {
- const int literal_cast_value_too_large =
- V <= limit_traits<T>::maximum_value;
- const int literal_cast_value_too_small =
- V >= limit_traits<T>::minimum_value;
- STATIC_ASSERT(literal_cast_value_too_large);
- STATIC_ASSERT(literal_cast_value_too_small);
- return T(V);
- }
literal_cast()使用了一个limits_traits类模板,它提供了成员常量(见15.5.3小节)minimum_value和maximum_value。其工作方式是将接受测试的常量当成所在编译器上的最大尺寸的有符号整型,即literal_cast_int_t。例如,32位编译器上的64位有符号整型。而后我们将该值跟强制目标类型的最小和最大值进行比较,并使用静态断言来断言比较的结果(见1.4.8小节)。
在进行比较时,所有值都会被提升为literal_cast_int_t,所以,对于那些比它小的类型,这种做法为该类型的值提供了一个周全的求值。假设literal_cast_int是64位的,考虑如下的代码:
- const int I = 200;
- sint8_t i8 = literal_cast<sint8_t, I>(); // 编译错误
- uint8_t ui8 = literal_cast<uint8_t, I>(); // 编译没问题
- sint16_t i16 = literal_cast<sint16_t, I>(); // 编译没问题
然而,将它用在向64位类型的转换上仍然是有所限制的,因为它使用了有符号64位类型作为常量的类型。如果你将转换目标类型指定为uint64_t,并传进一个比有符号最大正值更大的值,你就会遭遇截断或符号转换问题。这是该技术的一个缺点,语言对此也不能提供任何帮助,因为我们无法逃避这样一个现实:我们无法找到比当前可用的最大类型还要大的类型。
惟一的解决方案是防止人们把literal_cast用在最大的无符号整型上。借助于局部特化,我们可以很轻易地做到这一点,但它要求我们将强制从函数形式转换成类模板的形式,如程序清单19.9中所示:
程序清单19.9
- #ifdef ACMELIB_64BIT_INT_SUPPORT
- typedef int64_t literal_cast_int_t;
- stypedef uint64_t invalid_int_t;
- #else /* ACMELIB_64BIT_INT_SUPPORT */
- typedef int32_t literal_cast_int_t;
- typedef uint32_t invalid_int_t;
- #endif /* ACMELIB_64BIT_INT_SUPPORT */
-
- template< . . . >
- class literal_cast
- {
- public:
- operator T () const
- {
- . . . // 原先实现的剩余部分
- }
- };
-
- template<literal_cast_int_t V>
- class literal_cast<invalid_int_t, V>
- {
- private:
- operator invalid_int_t () const
- {
- const int cannot_literal_cast_to_largest_unsigned_integer = 0;
- STATIC_ASSERT(cannot_literal_cast_to_largest_unsigned_integer);
- return 0;
- }
- };
现在,我们针对invalid_int_t类型,即最大的无符号整型进行了特化,该特化将它的转换操作符设为私有的,从而防止用户将该强制用在invalid_int_t上。作为一个额外的好处,该操作符还包含了一个静态断言(见12.4.8小节),该断言会给那些毫无戒备的开发者提供一点额外的帮助。如果没有这个断言,它们只会得到类似"operator is inaccessible(操作符是不可访问的)"这样的含糊的编译错误信息。
现在,在代码中,你可以将字面量转换为任何类型(除了最大的无符号整型外)而不用担心截断问题了。
在结束这一节之前,我想提一下,Boost库包含了literal_cast的一个运行期的变体,称为numeric_cast,它是由Kevlin Henney编写的。所以,不管你想要在编译期还是运行期侦测出截断行为,你都可以得到相应的设施。