如何将Boost数量数组类型转换为底层类型?

6
我正在构建一个动态动画和渲染系统,希望使用Boost.Units来表示物理量,以获得良好的维度安全性。然而,我将不得不向不知道Boost的函数传递数量数组,例如:
  • OpenGL缓冲区填充命令。它们只需要一个const void *,并期望在解引用时找到floatdouble值的数组。它们读取数据。

  • 来自BLAS和LAPACK不同实现的线性代数函数(如gemmgesv)。它们通常接受指向给定数组的float *double *。它们同时读取和写入数据。

我知道boost::units::quantity<U, T>有一个const T& value()成员,它可以直接引用包含的T值。我还验证了boost::units::quantity<U, T>是一个标准布局结构体,只有一个非静态数据成员,类型为T
因此,假设对于boost::units::quantity<U, T> q,以下内容成立:
  • static_cast<const void*>(&q) == static_cast<const void*>(&q.value())
  • sizeof(q) == sizeof(T)
我的问题是:给定一个数组boost::units::quantity<U, T> a[100];,是否可以安全地执行以下操作:
  1. &a[0].value() 传递给一个期望在地址处读取100个类型为 T 的对象数组的函数?

  2. reinterpret_cast<T*>(&a[0]) 传递给一个将写入100个连续类型为 T 的值到地址的函数?

我很清楚这可能是未定义行为,但现在我必须遵循“实用性胜过纯洁性”(1)原则。即使这是UB,它是否会按预期执行,或者会以不可预见的方式咬人?由于这可能是特定于编译器的:我需要现代MSVC(来自VS 2015)。

如果这不安全,是否有一种方法可以安全地实现此操作?其中“this”指的是“在OpenGL和仅具有C接口的数值计算机中使用Boost.Units”,而无需不必要地复制数据。


(1) 改编自Python之禅


我认为你说UB的地方应该是IB,因为没有人应该对UB置之不理。 - sehe
具有讽刺意味的是,这些函数最终以基本上是void*接口的形式出现。因此,在编译这些外部库时已经做出了关于别名的决策。因此,您真正可以选择在哪里建立您的别名规则(如果有),这些规则最终将被二进制可执行文件忽略。C++缺少的是一种良好的表达方式。 - alfC
1个回答

3

是的,这看起来像是你可以做的事情。

但有一件事情你没有提到,应该将其添加到检查条件列表中:包装类型的对齐方式应与基础类型的对齐方式相匹配。(详见alignof)。

因此,在实践中,我会编写类似于以下代码的静态断言¹来保护使重新解释有效的假设。

如果添加了T与remove_cv_t<decltype(q.value())>相同的断言,则应该是可靠的。

在采取这些预防措施后,因为reinterpret_cast在你特定的平台上的语义,应该不会出现UB,只会出现IB(实现定义行为)。

¹ 也许还需要一个调试断言:&q.value() == &q


1
但是违反严格别名规则是未定义行为(UB),而不是不良行为(IB)。(它在调用的函数中被违反,也就是BLAS/LAPACK/OpenGL C代码违反了它。) - dyp
嗯,有这个方面。我不知道是否必然是一个问题,但肯定是需要研究的领域。 - sehe
@dyp 我也担心严格别名问题。同时,在该地址上实际上确实存在一个 float 类型的对象(作为类型为 quantity 的完整对象的子对象)。因此,也许由于这个原因没有违反严格别名规则? - Angew is no longer proud of SO
关于别名规则的重点在于编译器是否必须意识到它以防止不安全的优化,据我所知。因此,即使类型游戏是有效的(当解释回到精确的原始类型时不是未定义行为),它可能会“掩盖”编译器的视野并导致它进行不安全的优化。我真的需要更多了解涉及的API。也许还有其他人可以提供帮助。 - sehe
1
@Angew 嗯,那是真的。C++11[class.mem]p20对这种情况有明确的异常处理;在C++14中,这个规则被重新表述了,但并不是为了禁止这种用法。不过关于指针算术运算,我还不是完全确定。 - dyp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接