C++11 解决方案
由于T 仅作为静态成员函数的返回类型出现在未专门化的::std::numeric_limits<T> 的声明中(参见C++03 18.2.1.1 和C++11 18.3.2.3),这就足够了确保这样做是声明安全的具体问题。
这导致编译时错误的原因是,模板参数的使用可能不会在模板特化的实例化中产生格式错误的构造(C++03 14.3/6,C+ +11 14.3/6)。
对于启用 C++11 的项目,Andy Prowl 的 can_be_returned_from_function 解决方案适用于所有相关情况:http://ideone.com/SZB2bj,但它不容易移植到 C++03 环境。当使用不完整的类型 (http://ideone.com/k4Y25z) 实例化时,它会导致错误。建议的解决方案将接受不完整的类,而不是导致错误。当前的微软编译器(msvc 1700 / VS2012)似乎不喜欢这个解决方案,无法编译。
Jonathan Wakely 提出了一个解决方案,该解决方案利用std::is_convertible<T, T> 来确定@987654333@ 是否可以作为函数的返回值。这也消除了不完整的类,并且很容易显示正确(它在 C++11 中定义为完全按照我们想要的方式执行)。执行表明所有已知有问题的情况(数组、未定义长度的数组、函数、抽象类)都被正确识别。作为奖励,它还可以正确识别不完整的类,标准不允许将其作为 numeric_limits 的参数(见下文),尽管它们在实践中似乎不会引起任何问题,只要实际上没有调用有问题的函数。测试执行:http://ideone.com/zolXpp。当前的一些编译器(icc 1310 和 msvc 1700,这是 VS2012 的编译器)使用这种方法生成的结果不正确。
Tom Knapen 的 is_arithmetic 解决方案是一个非常简洁的 C++11 解决方案,但需要专门化 numeric_limits 的类型的实现者也专门化 is_arithmetic。或者,在其基本情况下继承自 is_arithmetic 的类型(此类型可能称为 numeric_limits_is_specialised)可能在这些情况下特化,因为特化 is_abstract 可能在语义上不正确(例如,未指定所有基本算术运算符,但仍然是有效的类整数类型)。
这种白名单方法确保即使是不完整的类型也能得到正确处理,除非有人恶意试图强制编译错误。
警告
如混合结果所示,C++11 支持仍然参差不齐,即使使用当前的编译器也是如此,因此您对这些解决方案的了解可能会有所不同。 C++03 解决方案将受益于更一致的结果以及在不希望切换到 C++11 的项目中使用的能力。
迈向稳健的 C++03 解决方案
C++11 8.3.5/8 段列出了对返回值的限制:
如果参数的类型包括“指向 T 的未知边界数组的指针”或“对 T 的未知边界数组的引用”形式的类型,则程序是非良构的。 函数不应具有数组或函数类型的返回类型,尽管它们可能具有指针或对此类事物的引用类型的返回类型。不应有函数数组,尽管可以有数组指向函数的指针。
在 C++11 8.3.5/9 段继续:
类型不应在返回或参数类型中定义。函数定义的参数类型或返回类型不应是不完整的类类型(可能是 cv 限定的),除非函数定义嵌套在该类的成员规范中(包括定义在类中定义的嵌套类中)。
这和C++03 8.3.5/6段差不多:
如果参数的类型包括“指向 T 的未知边界数组的指针”或“对 T 的未知边界数组的引用”形式的类型,则程序是非良构的。 函数不应具有数组或函数类型的返回类型,尽管它们可能具有指针类型或对此类事物的引用的返回类型。尽管可以有指向函数的指针数组,但不应有函数数组。类型不得
在返回或参数类型中定义。参数的类型或函数定义的返回类型不应是不完整的类类型(可能是 cv 限定的),除非函数定义嵌套在该类的成员规范中(包括定义在类中定义的嵌套类中)。
在 C++11 10.4/3 和 C++03 10.4/3 中同样提到了另一种有问题的类型:
抽象类不得用作参数类型、函数返回类型或显式转换的类型。 [...]
有问题的函数没有嵌套在不完整的类类型中(::std::numeric_limits<T> 除外,它不能是它们的T),所以我们有T 的四种问题值:数组、函数、不完整的类类型和抽象类类型。
数组类型
template<typename T> struct is_array
{ static const bool value = false; };
template<typename T> struct is_array<T[]>
{ static const bool value = true; };
template<typename T, size_t n> struct is_array<T[n]>
{ static const bool value = true; };
检测T 是数组类型的简单情况。
不完整的类类型
有趣的是,不完整的类类型不会仅仅从实例化中导致编译错误,这意味着要么测试的实现比标准更宽容,要么我遗漏了一些东西。
C++03 示例:http://ideone.com/qZUa1N
C++11 示例:http://ideone.com/MkA0Gr
由于我无法想出正确的方法来检测不完整类型,甚至标准都指定了(C++03 17.4.3.6/2 item 5)
特别是,在以下情况下效果未定义:[...] 如果在实例化模板组件时将不完整类型 (3.9) 用作模板参数。
在 C++11 (17.6.4.8/2) 中仅添加以下特殊津贴:
[...] 除非该组件特别允许
假设任何将不完整类型作为模板参数传递的人都是独立的,这似乎是安全的。
C++11 允许不完整类型参数的情况的完整列表非常简短:
declval
unique_ptr
-
default_delete (C++11 20.7.1.1.1/1: "类模板 default_delete 用作类模板 unique_ptr 的默认删除器(销毁策略)。"
shared_ptr
weak_ptr
enable_shared_from_this
抽象类和函数类型
检测函数比在 C++11 中要多一些工作,因为我们在 C++03 中没有可变参数模板。但是,上面对函数的引用已经包含了我们需要的提示;函数可能不是数组的元素。
C++11 8.3.4\1 段包含句子
T 称为数组元素类型;此类型不应是引用类型、(可能是 cv 限定的)类型 void、函数类型或抽象类类型。
这在 C++03 8.3.4\1 段落中也是逐字记录的,它允许我们测试一个类型是否是一个函数类型。检测(cv) void 和引用类型很简单:
template<typename T> struct is_reference
{ static const bool value = false; };
template<typename T> struct is_reference<T&>
{ static const bool value = true; };
template<typename T> struct is_void
{ static const bool value = false; };
template<> struct is_void<void>
{ static const bool value = true; };
template<> struct is_void<void const>
{ static const bool value = true; };
template<> struct is_void<void volatile>
{ static const bool value = true; };
template<> struct is_void<void const volatile>
{ static const bool value = true; };
使用这个,为抽象类类型和函数编写元函数很简单:
template<typename T>
class is_abstract_class_or_function
{
typedef char (&Two)[2];
template<typename U> static char test(U(*)[1]);
template<typename U> static Two test(...);
public:
static const bool value =
!is_reference<T>::value &&
!is_void<T>::value &&
(sizeof(test<T>(0)) == sizeof(Two));
};
请注意,如果希望区分is_function 和is_abstract_class,可以使用以下元函数来区分两者
template<typename T>
class is_class
{
typedef char (&Two)[2];
template<typename U> static char test(int (U::*));
template<typename U> static Two test(...);
public:
static const bool value = (sizeof(test<T>(0)) == sizeof(char));
};
解决方案
结合前面所有的工作,我们可以构造is_returnable元函数:
template<typename T> struct is_returnable
{ static const bool value = !is_array<T>::value && !is_abstract_class_or_function<T>::value; };
C++03 (gcc 4.3.2) 的执行:http://ideone.com/thuqXY
C++03 (gcc 4.7.2) 的执行:http://ideone.com/OR4Swf
C++11 (gcc 4.7.2) 的执行:http://ideone.com/zIu7GJ
正如预期的那样,除了不完整类之外的所有测试用例都会产生正确的答案。
除了上述测试运行之外,此版本还经过测试(使用完全相同的测试程序)以产生相同的结果,而不会出现以下警告或错误:
- MSVC 1700(VS2012 带和不带 XP 配置文件)、1600 (VS2010)、1500 (VS2008)
- ICC 赢 1310
- GCC(C++03 和 C++11/C++0x 模式)4.4.7、4.6.4、4.8.0 和 4.9 快照
任何一种情况的限制
请注意,尽管任一版本中的这种方法适用于任何未扩展标准中所示实现的numeric_limits 实现,但它绝不是一般问题的解决方案,实际上理论上可能会导致奇怪但符合标准的实现(例如添加私有成员的实现)的问题。
不完整的类仍然是一个问题,但要求比标准库本身更高的健壮性目标似乎很愚蠢。