【问题标题】:How should I construct an std::array<T, k> from a U with sizeof(U) = k * sizeof(T)?我应该如何使用 sizeof(U) = k * sizeof(T) 从 U 构造 std::array<T, k>?
【发布时间】:2016-05-16 17:48:45
【问题描述】:

我想将 U 类型的单个变量表示为 T 类型元素的数组,以便在编译时使用。 sizeof(T) 完美地划分了 sizeof(U),所以让 k = sizeof(U)/sizeof(T) 应该是std::array&lt;T, k&gt;

问题是,我如何构造它(同样,在编译时)?我可以/应该使用铸造,即

* (reinterpret_cast<std::array<T,k> *>(&my_u))

或者可能是某种递归函数调用来设置它的元素?还是有更好/更好的方法?

【问题讨论】:

  • 你为什么要这个?
  • 听起来很像一个位域,甚至可能是一个联合。
  • 不可能;你不能在编译时reinterpret_cast
  • @GManNickG:为了便于讨论,假设我有一些 constexpr 函数,它采用 T 类型的元素,我想将它应用于任意大小的类型(我不能只是重写它函数)。
  • @ecatmur:好的,因此我的问题是关于更好的选择。递归肯定是可行的。

标签: templates c++11 constructor stdarray


【解决方案1】:

别名值表示的正确方法几乎总是使用memcpy

std::array<T, k> my_t;
static_assert(sizeof(my_t) == sizeof(my_u), "!!");
std::memcpy(my_t.data(), &my_u, sizeof(my_u));

没有“更好”的方法,因为您有时需要使用 memcpy 来绕过严格的别名限制(除非 T 是窄字符类型)。

在编译时,这通常是不可能的,因为 C++ 标准没有,例如描述浮点类型中位的布局,或者整数类型是小端、大端还是混合端。这在某些受限情况下可能是可行的,但您将无法使用memcpyreinterpret_cast,因此必须编写特定类型的代码来单独访问U 的成员。

构建代码的最佳方式可能是编写一组函数,从特定偏移处的 U 类型的值中提取 T 类型的值:

constexpr T getValueOfTAtOffset(U u, std::integral_constant<std::size_t, 0>) { ... }
constexpr T getValueOfTAtOffset(U u, std::integral_constant<std::size_t, 1>) { ... }
// ...

然后您将使用index_sequence 模板推断调用它们:

template<std::size_t... I>
constexpr std::array<T, sizeof...(I)> asArrayOfTImpl(U u, std::integer_sequence<std::size_t, I...>) {
    return {getValueOfTAtOffset(u, std::integral_constant<std::size_t, I>{})};
}
constexpr std::array<T, k> asArrayOfT(U u) {
    return asArrayOfTImpl(u, std::make_index_sequence<k>{});
}

【讨论】:

  • C++ 标准没有,但假设它是不可移植的机器相关代码,我知道位布局和字节序。
  • @einpoklum 是constexpr?签名是什么?
  • 您上一条评论中的“它”是什么?如果您的意思是接受 T 类型元素的函数 - 不,它不是 constexpr,实际上它不是函数而是其他东西;问题是我只能使用 constexpr 代码,因为它不是您的标准内存模型,本身没有 memcpy(),几乎所有 constexpr 代码最终都必须被优化掉。
【解决方案2】:

C++(和 C)有一个称为严格别名的特性。

严格的别名表明指向T 的指针和指向U 的指针永远不会指向相同的数据。

此规则背后的一个原因是它允许进行大量优化。如果您有一个 int 并且您取消引用 short* 并对其进行修改,那么您知道 int 没有被修改而无需跟踪指针。

这意味着您只能将 X* 重新解释为 Y* 如果指针最初实际上是 Y*其中一个例外适用.

这些例外情况包括 Ychar 类型,或者布局兼容性保证基本上谈论在某些标准布局情况下一个结构是另一个结构的前缀。

在一般情况下,这些不适用。因此,您必须找出这些例外之一,或者使用 memcpy 通过这些例外之一整理您的数据,以符合标准。

这里的风险是,在面对明显的未定义行为时,激进的编译器优化可能会显着改变代码的明显含义。即使您对其进行了测试并且它可以工作,下一个编译器版本也有理由使您的代码以各种不同的方式或无害的编译器选项标志或其他任何方式中断。

您可以做的一个选择是创建一个union。但即使在那里,除非标准布局保证,否则访问联合的“错误”部分是未定义的行为。

关于您的数据的更具体的信息集实际上可能会提供一种解决方法 - 例如,您可能满足标准布局保证 - 但它们很容易被破坏。

【讨论】:

    猜你喜欢
    • 2014-11-30
    • 2022-01-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-12
    • 2016-02-25
    • 1970-01-01
    相关资源
    最近更新 更多