10

类模板::std::numeric_limits<T>只能为 types 实例化T,它可以是函数的返回值,因为它总是定义类似的成员函数static constexpr T min() noexcept { return T(); }有关非-c++03 或 c++11 中的专用版本)。

If Tis ieint[2]实例化将立即导致编译时错误,因为int[2]不能是函数的返回值。

使用安全版本进行包装::std::numeric_limits很容易 -如果知道确定实例化是否安全的方法::std::numeric_limits这是必要的,因为如果可能的话,应该可以访问有问题的功能。

明显(并且明显错误)的测试方式::std::numeric_limits<T>::is_specialised不起作用,因为它需要实例化有问题的类模板。

有没有办法测试实例化的安全性,最好不枚举所有已知的错误类型?甚至可能是确定任何类模板实例化是否安全的通用技术?

4

3 回答 3

6

关于决定是否可以为函数返回类型的类型特征,我将这样做:

#include <type_traits>

template<typename T, typename = void>
struct can_be_returned_from_function : std::false_type { };

template<typename T>
struct can_be_returned_from_function<T,
    typename std::enable_if<!std::is_abstract<T>::value,
    decltype(std::declval<T()>(), (void)0)>::type>
    : std::true_type { };

另一方面,正如 Tom Knapen 在评论中所建议的,您可能希望使用std::is_arithmetic标准类型特征来确定您是否可以专注numeric_limits于某种类型。

根据numeric_limits类模板上 C++11 标准的第 18.3.2.1/2 段,实际上:

应为每种算术类型提供专门化,包括浮点数和整数,包括bool. 该成员is_specialized应为所有此类专业的真实身份numeric_limits

于 2013-05-12T09:18:45.130 回答
5

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_specialisedis_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_functionis_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未扩展标准中所示实现的任何实现,但它绝不是一般问题的解决方案,实际上理论上可能会导致奇怪但符合标准的问题实现(例如添加私有成员的实现)。

不完整的类仍然是一个问题,但要求比标准库本身更高的健壮性目标似乎很愚蠢。

于 2013-05-12T16:26:05.343 回答
4

std::is_convertible<T, T>::value将告诉您是否可以从函数返回类型。

is_convertible<T1, T2>T2是根据从类型表达式返回的函数来定义的T1

#include <limits>
#include <type_traits>

struct Incomplete;
struct Abstract { virtual void f() = 0; };

template<typename T>
  using is_numeric_limits_safe = std::is_convertible<T, T>;

int main()
{
  static_assert(!is_numeric_limits_safe<Incomplete>::value, "Incomplete");
  static_assert(!is_numeric_limits_safe<Abstract>::value,   "Abstract");
  static_assert(!is_numeric_limits_safe<int[2]>::value,     "int[2]");
}

这可能不是您想要的,因为只要您不调用任何按值返回的函数,实例化是安全的。std::numeric_limits<Incomplete>但是无法实例化std::numeric_limits<int[2]>

这是一个更好的测试(使用 SFINAE),它给出了is_numeric_limits_safe<Incomplete>::value==true

template<typename T>
class is_numeric_limits_unsafe
{
  struct mu { };

  template<typename U>
    static U test(int);

  template<typename U>
    static mu test(...);

public:
    typedef std::is_same<decltype(test<T>(0)), mu> type;
};

template<typename T>
struct is_numeric_limits_safe
: std::integral_constant<bool, !is_numeric_limits_unsafe<T>::type::value>
{ };
于 2013-05-12T19:11:08.320 回答