确定:: std :: numeric_limits <t>是否可以安全地实例化</t>

时间:2013-05-12 09:05:14

标签: c++ templates c++11 c++03

类模板::std::numeric_limits<T>只能为类型T实例化,它可以是函数的返回值,因为它总是定义成员函数,如static constexpr T min() noexcept { return T(); }(参见http://www.cplusplus.com/reference/limits/numeric_limits/有关c ++ 03或c ++ 11中非专用版本的更多信息,请参阅。

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

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

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

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

3 个答案:

答案 0 :(得分: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 { };

另一方面,as suggested by Tom Knapen in the comments,您可能希望使用std::is_arithmetic标准类型特征来确定是否可以为某种类型专门化numeric_limits

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

  

应为每个算术类型提供专业化,包括浮点和整数,包括bool。   成员is_specialized对于numeric_limits的所有此类专业都应该是真实的。

答案 1 :(得分:5)

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)实例化时,它会导致错误。建议的解决方案将接受不完整的类而不是导致错误。当前的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段列出了返回值的限制:

  

如果参数的类型包括表格的类型&#34;指向T&#34的未知界限数组的指针;或者&#34;引用T&#34;的未知界限数组,该程序是不正确的。 函数不应该具有类型数组或函数的返回类型,尽管它们可能具有类型指针的返回类型或对此类事物的引用。不应该有函数数组,尽管可以有数组函数指针。

并在C ++ 11 8.3.5 / 9段中继续:

  

不应在返回或参数类型中定义类型。函数定义的参数类型或返回类型不应是不完整的类类型(可能是cv限定的),除非函数定义嵌套在该类的成员规范中(包括定义)在类中定义的嵌套类中。)

这与段落C ++ 03 8.3.5 / 6几乎相同:

  

如果参数的类型包括表格的类型&#34;指向T&#34的未知界限数组的指针;或者&#34;引用T&#34;的未知界限数组,该程序是不正确的。 函数不应具有类型数组或函数的返回类型,尽管它们可能具有类型指针的返回类型或对此类事物的引用。虽然可以有函数指针数组,但是不应该有函数数组。类型不得   在返回或参数类型中定义。函数定义的参数类型或返回类型不应是不完整的类类型(可能是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第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:&#34;类模板default_delete用作类模板unique_ptr的默认删除器(销毁策略)。&# 34;
  • shared_ptr
  • weak_ptr
  • enable_shared_from_this

抽象类&amp;功能类型

检测函数比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)
  • ICC Win 1310
  • GCC(C ++ 03和C ++ 11 / C ++ 0x模式)4.4.7,4.6.4,4.8.0和4.9快照

任何一种情况的限制

请注意,虽然这两种版本中的这种方法适用于任何numeric_limits实现,但并未扩展到标准中所示的实现,但它绝不是解决一般问题的方法,实际上理论上可能是导致奇怪但符合标准的实现(例如添加私有成员的实现)的问题。

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

答案 2 :(得分: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>
{ };