【问题标题】:Do I need to static_cast my numeric template values?我需要静态转换我的数字模板值吗?
【发布时间】:2018-07-26 09:18:42
【问题描述】:

我目前正在编写一个模板化的 c++ 物理库。在我的函数中,我经常需要明确比较或设置某些数值。我的目标是编写尽可能通用的库,因此我希望尽可能支持浮点和整数类型。

为了获得正确的类型,我经常在我的代码中使用对T 的显式强制转换。在我的所有情况下,这当然被解释为static_cast。因此,我的问题是:我真的需要static_cast 这些值吗?或者我是否可以通过做/不做来获得运行时开销?

一个例子:

我目前有这样的功能:

template <class T> auto is_elliptic(T eccentricity, T semi_major_axes = T(1)) {
   return T(0) <= eccentricity < T(1) && T(0) < semi_major_axes;
}

不过,我也可以这样写:

template <class T> auto is_elliptic(T eccentricity, T semi_major_axes = T(1.0)) {
   return T(0.0) <= eccentricity < T(1.0) && T(0.0) < semi_major_axes;
}

像这样:

template <class T> auto is_elliptic(T eccentricity, T semi_major_axes = 1) {
   return 0 <= eccentricity < 1 && 0 < semi_major_axes;
}

或者像这样:

template <class T> auto is_elliptic(T eccentricity, T semi_major_axes = 1.0) {
   return 0.0 <= eccentricity < 1.0 && 0.0 < semi_major_axes;
}

我不太关心这两个版本的可读性。我也不关心这样一个事实,在这里使用整数类型可能是没用的。我只想知道:

  • 这些实现之间有区别吗?
  • 如果是,是什么?
  • 另外,此代码编译器上可能的优化是否取决于?

编辑:

  • 如果我想支持自定义数字类型,是否会进行任何更改?

编辑:

正如 cmets 中所指出的,上面使用的链式比较实际上是错误的。代码应该是这样的:

return T(0) <= eccentricity && eccentricity < T(1) && T(0) < semi_major_axes;

此外,可以使用constexpr 版本对代码进行运行时优化:

template <class T> constexpr auto is_elliptic(T eccentricity) {
    return T(0) <= eccentricity && eccentricity < T(1);
}
template <class T> constexpr auto is_elliptic(T eccentricity, T semi_major_axes) {
    return T(0) <= eccentricity && eccentricity < T(1) && T(0) < semi_major_axes;
}

【问题讨论】:

  • 哎呀,链式比较。 99.99% 的时间都是错误的。
  • 您可以声明一些具有适当类型的有意义的常量,而不是使用幻数,并在每次需要该值时使用它们而不是强制转换。
  • “在我的所有情况下,这当然被解释为static_cast。” ——你没有考虑的情况呢?当T 是一个强类型枚举,或者可能是一个指针,因为有人不小心传入了错误的参数,那又会怎样呢?你想默默地接受这一点,还是想让你的编译器告诉你你做错了什么?
  • 嗯,除了在 cmets 中已经指出的内容之外,我还编写了两个 (constexpr) 版本,一个带有 semi_major_axes,一个没有那个比较,而不是一个带有默认值的函数参数。

标签: c++ templates type-conversion static-cast


【解决方案1】:

取决于你想要什么。


此答案假定Tintfloat(尽管它适用于doublelong 或具有类似行为的自定义类型),并且x 的类型为T .

  • 如果使用演员表:

这很容易理解。但是,请注意常量在T 类型中不可表示的情况。 0.5&gt;x(int)0.5&gt;x 不同。

  • 如果不使用演员表:

[I]如果提升的操作数具有不同的类型,则应用额外的一组隐式转换,称为通常的算术转换,目标是生成通用类型 [...]。

(来自https://en.cppreference.com/w/cpp/language/operator_arithmetic

| T     | Comparison | Equivalent to |
+-------+------------+---------------+
| int   | 0<x        | 0<x           |
| int   | 0.f<x      | 0.f<(float)x  |        (*)
| float | 0<x        | (float)0<x    |
| float | 0.f<x      | 0.f<x         |

对于(*),比较在float 上完成,而转换常量 (int(0.f&lt;x)) 将在整数上进行比较。其他情况也一样。

关于编译器开销:编译可能会稍微慢一点,但考虑到编译标准库头文件的速度有多慢,应该没什么关系。

【讨论】:

  • 您的示例在类型安全方面很有意义。在(*) 的情况下,我得到了一个浮点比较,尽管我可能想要一个整数。标准中可能有关于在什么情况下使用哪种类型比较的内容。你有没有这个链接?另外,我指的不是编译时间开销,而是运行时间开销,因为编译速度是否慢一点对我来说并不重要……你能谈谈运行时间开销吗?
  • 我找到了关于类型转换的标准信息herehere。但是,我仍然不确定运行时开销。
  • 在尝试了godbolt的一些变种后,我发现可能会发生不必要的转换。特别是如果常量的来源对编译器有些不透明。因此,运行时开销是可能的!
  • @jan.sende 我不确定您所说的“常量的来源对编译器有些不透明”是什么意思。你的代码是什么?
  • 如果我将数字直接放入代码中,编译器可以优化转换。但是,如果我从其他来源获得号码,它可能不会。比较:auto test() { return int(1.0); } 与:auto one() { return 1.0; }auto test() { return int(one()); } 这是相同的,但在没有优化的情况下,在第二种情况下,您会得到从 doubleint 的不必要转换。 (cvttsd2si 在程序集中)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-22
  • 2014-02-06
  • 1970-01-01
  • 2012-02-09
  • 2012-10-29
相关资源
最近更新 更多