在具有兼容布局的 std::vector 底层缓冲区上使用 std::bit_cast

Using std::bit_cast on a std::vector underlying buffer with compatible layout

提问人:Stefano Bellotti 提问时间:10/25/2023 最后编辑:Stefano Bellotti 更新时间:10/25/2023 访问量:86

问:

我需要使用 C++ 库中不同类型实体的整数标识符包装 C 库。 我希望通过我的库同时实现类型安全性和效率(理想情况下是零开销)。

本机 C 库具有类似于以下内容的功能:

int ps_get_bodies_bounds(int const* bodies, int size, ...);
int ps_get_faces_bounds(int const* faces, int size, ...);

正如你所看到的,s到处都被用来表示各种实体。int

在我的 C++ 库中,我想使用类型系统来表达这些不同的实体:

class Body
{
   public:

      constexpr Body() noexcept
      {
         // enforce at compile time that this class
         // must be just a strongly typed integer
         static_assert(sizeof(Body) == sizeof(int));
      }

      // ... public API ...

   private:
      int m_tag{ 0 };
};

class Face
{
   // similar to what has been done for 'Body'
};

C++包装器的目标之一是客户端可以使用连续的内存容器,例如存储实体并对其进行操作:std::vector

wrapper::Bounds wrapper::get_bounds(std::vector<Body> const& bodies);
wrapper::Bounds wrapper::get_bounds(std::vector<Face> const& faces);

包装器当然应该调用底层 C 函数,理想情况下,出于效率原因,我希望能够直接传递向量持有的内存:

wrapper::Bounds wrapper::get_bounds(std::vector<Body> const& bodies)
{
   // ...

   // OF COURSE, DOES NOT COMPILE
   auto err = ps_get_bodies_bounds(bodies.data(), static_cast<int>(bodies.size()), ...);

   // ...
}

以这种方式编写,代码当然不会编译,因为返回的是类型而不是.bodies.data()Body const*int const*

在这种情况下,它是否是一种定义明确的行为/安全/可移植?std::bit_cast

ps_get_bodies_bounds(std::bit_cast<int const*>bodies.data(), ...);

如果是,它甚至适用于像 ?std::span<Body>

请注意,我在编译时强制 、 等的大小......匹配 .BodyFaceint

我已经在带有 GCC、Clang 和 MSVC 的编译器资源管理器上尝试了这种方法,它似乎有效(代码比我在这里解释的要复杂一些,但核心概念是相同的)。我不确定它是否是 UB 的有效使用。这是 Godbolt 的链接:https://godbolt.org/z/44GWc7PYsstd::bit_cast

提前感谢您的帮助。

C++ 包装器

评论

1赞 chrysante 10/25/2023
不可以,不能用于从一种指针类型强制转换为另一种指针类型,并期望最终得到有效的指针。如果我理解正确,您想要的是将 s 数组重新解释为 s 数组。据我所知,根据 C++ 标准,这始终是未定义的行为。但是,在实践中,您应该可以使用简单的 ,特别是如果 C 代码与 C++ 代码分开编译。如果任何现有的编译器对此做了一些有趣的事情,我会感到惊讶,只是标准没有提供任何保证。bit_castBodyintreinterpret_cast<int const*>(bodies.data())
3赞 chrysante 10/25/2023
C++23 提供了 start_lifetime_asstart_lifetime_as_array 函数,这将明确支持重新解释强制转换的使用。如果您使用 C++23 或计划迁移,则可以使用它。
0赞 Pepijn Kramer 10/25/2023
@chrysante 虽然正式地说这是UB,但我同意你的评估。如果它适用于您的编译器,请编写编译时测试(已完成)和单元测试,以便稍后快速捕获(编译器)行为的任何更改。
0赞 Pepijn Kramer 10/25/2023
另一种选择是不让 Body 直接成为 int。但是要给 body 一个静态的 std::vector<int> 作为 body 类的成员,并且每个 body 实例都引用这个向量中的索引。它可以对此进行操作。
1赞 Davis Herring 10/26/2023
@chrysante:请注意,在让它们摧毁它们之前,您必须将它们恢复到原始类型。vector

答: 暂无答案