【问题标题】:Determining if ::std::numeric_limits<T> is safe to instantiate确定 ::std::numeric_limits<T> 是否可以安全实例化
【发布时间】:2013-05-12 09:05:14
【问题描述】:

类模板::std::numeric_limits&lt;T&gt;只能被实例化为类型T,它可以是函数的返回值,因为它总是定义像static constexpr T min() noexcept { return T(); }这样的成员函数(参见http://www.cplusplus.com/reference/limits/numeric_limits/以了解更多关于非c++03 或 c++11 中的专用版本)。

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

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

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

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

【问题讨论】:

  • std::is_arithmetic 有什么问题?使用示例可以找到here
  • @TomKnapen,为什么不作为答案发布?
  • @hmjd 因为我不确定我的答案是否正确(有时我误解了一个问题并发布了完全不相关的评论/答案)。
  • @TomKnapen 一个非常简洁的解决方案,如果没有专门化 numeric_limits 而没有专门化 is_arithmetic,这将导致一种类型枚举(安全类型)。
  • @TomKnapen 这不会使用专门的 std::numeric_limits 捕获自定义类型,这是一种非常常见的技术。

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


【解决方案1】:

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

#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。 对于numeric_limits 的所有此类专业化,成员is_specialized 应为真。

【讨论】:

  • 这还将报告true 任何可复制的内容。我不确定这是 OP 想要的。
  • 由于我不会调用有问题的函数,因此对于不可复制构造(且不可默认构造)类型返回 true 实际上是可以的。事实上,我不知道除了数组之外的任何类型都会导致这个问题。但是,我想知道是否有一种方法可以测试模板是否可实例化,而不是考虑哪种类型(例如可以从函数返回的类型)会导致特定模板出现问题。
  • @dionadar:老实说,我认为您可能只使用is_arithmetic,它更简单,意图明确,并由标准引号支持(见答案)
  • 有时重载numeric_limits 是有意义的(例如,这个用户stackoverflow.com/questions/1615197/… 就是这样做的)。使用 is_arithmetic 将需要任何此类情况也将 is_arithmetic 专门化为 true (当我阅读定义时,这样做可能是错误的)。由于我必须处理来自 C++03 代码库的旧代码,其中 is_arithmetic 不存在,所以这一点变得没有意义。
  • 在使用 C++03 解决方案时,我遇到了您的解决方案的一个问题:它似乎接受抽象类,它不能从函数返回,因此在用作numeric_limits的类型参数
【解决方案2】:

C++11 解决方案

由于T 仅作为静态成员函数的返回类型出现在未专门化的::std::numeric_limits&lt;T&gt; 的声明中(参见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&lt;T, T&gt; 来确定@​​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&lt;T&gt; 除外,它不能是它们的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 实现,但它绝不是一般问题的解决方案,实际上理论上可能会导致奇怪但符合标准的实现(例如添加私有成员的实现)的问题。

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

【讨论】:

  • 您的不完整类的测试用例不会出错,因为它不会立即实例化成员函数定义。只有声明被实例化,这允许不完整的类类型。
  • 对于不完整的类类型,标准中的措辞似乎非常相似,例如数组类型(“不应拥有”与“不应拥有”)?
  • 你错过了我的意思。其中一种情况仅适用于非定义声明。其他情况适用于任何类型的函数声明。 incomplete f (); 完全没问题。
  • 也许我的词汇有误,但是static constexpr T min() noexcept { return T(); }(标准版本)和static _Ty (min)() _THROW0() { return (_Ty(0)); }(我的编译器头文件的实际版本)不是定义,因为任何一个版本都包含正文?跨度>
  • 是的,两者都是定义。但是如果你去掉定义它们的部分(主体),那么你最终会得到立即实例化的部分。数组类型仍然会导致错误。而不完整的类类型则不会。
【解决方案3】:

std::is_convertible&lt;T, T&gt;::value 将告诉您是否可以从函数返回类型。

is_convertible&lt;T1, T2&gt; 是根据一个函数定义的,该函数返回从 T1 类型的表达式转换而来的 T2。

#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&lt;Incomplete&gt; 是安全的。但无法实例化std::numeric_limits&lt;int[2]&gt;。

这是一个更好的测试(使用 SFINAE),它给出了is_numeric_limits_safe&lt;Incomplete&gt;::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>
{ };

【讨论】:

  • 我真的很喜欢你的第一个解决方案,它很好地抓住了问题的核心。但是,我无法重现您的第二个解决方案,它似乎会导致抽象或不完整类型的编译错误:ideone.com/OkmCBG
  • 这看起来像一个 G++ 4.7 错误,它可以用 G++ 4.9 或 Clang 编译
  • ... 并使用 G++ 4.8.1 编译成功
  • 使用 icc 1310 和 MSVC 1700 (VS2012) 测试了两个版本:版本 1 在两种情况下都产生了不正确的结果(icc:始终为真,msvc:除函数和不完整的类外为真);版本 2 产生正确的结果,但会导致 icc 发出警告。
  • 版本 2 可能取决于实现 open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3276.pdf 的编译器 ...虽然不确定
猜你喜欢
  • 2011-07-31
  • 2014-12-29
  • 1970-01-01
  • 2018-07-20
  • 2017-04-02
  • 1970-01-01
  • 2014-11-20
  • 2023-04-11
  • 2012-12-21
相关资源
最近更新 更多