C++11 解决方案
由于T
仅作为静态成员函数的返回类型出现在 unspecialised 的声明中::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 )实例化时,它会导致错误。建议的解决方案将接受不完整的类,而不是导致错误。当前的 Microsoft 编译器 (msvc 1700 / VS2012) 似乎不喜欢这种解决方案并且无法编译。
Jonathan Wakely 提出了一种解决方案,该解决方案通过利用std::is_convertible<T, T>
来确定是否T
可以是函数的返回值。这也消除了不完整的类,并且很容易显示正确(它在 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 中同样提到了另一种有问题的类型:
抽象类不得用作参数类型、函数返回类型或显式转换的类型。[...]
有问题的函数没有嵌套在不完整的类类型中(除了 of ::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(带有和不带 XP 配置文件的 VS2012)、1600 (VS2010)、1500 (VS2008)
- 国际商会赢 1310
- GCC(C++03 和 C++11/C++0x 模式)4.4.7、4.6.4、4.8.0 和 4.9 快照
任何一种情况的限制
请注意,尽管任一版本中的这种方法适用于numeric_limits
未扩展标准中所示实现的任何实现,但它绝不是一般问题的解决方案,实际上理论上可能会导致奇怪但符合标准的问题实现(例如添加私有成员的实现)。
不完整的类仍然是一个问题,但要求比标准库本身更高的健壮性目标似乎很愚蠢。