C++11:将结构数组重新解释为结构成员数组
C++11: reinterpreting array of structs as array of struct's member
考虑以下类型:
struct S
{
char v;
};
给定一个 const S
的数组,是否有可能 以符合标准的方式,将其重新解释为 const char
的数组,其元素对应到每个原始数组元素的成员 v
的值,反之亦然?例如:
const S a1[] = { {'a'}, {'4'}, {'2'}, {'[=11=]'} };
const char* a2 = reinterpret_cast< const char* >(a1);
for (int i = 0; i < 4; ++i)
std::cout << std::boolalpha << (a1[i].v == a2[i]) << ' ';
上面的代码是可移植的吗?它会打印 true true true true
吗?如果没有,还有其他方法可以实现吗?
显然,可以创建一个新数组并用原数组的每个元素的成员v
初始化它,但整个思路是避免创建一个新数组。
平凡,不 - struct
可能有填充。这完全打破了对数组的任何重新解释。
形式上 struct
可能有填充,因此其大小大于 1。
也就是说,形式上你不能 reinterpret_cast
并且拥有完全可移植的代码,除了¹只有一个项目的数组。
但是对于实践,几年前有人问现在是否有任何编译器默认会为 struct T{ char x; };
提供 sizeof(T) > 1
。我还没有看到任何例子。所以在实践中,可以 static_assert
大小为 1,根本不用担心这个 static_assert
会在某些系统上失败。
即
S const a1[] = { {'a'}, {'4'}, {'2'}, {'[=10=]'} };
static_assert( sizeof( S ) == 1, "!" );
char const* const a2 = reinterpret_cast<char const*>( a1 );
for( int i = 0; i < 4; ++i )
{
assert( a1[i].v == a2[i] );
}
因为 可能 以索引具有未定义行为的方式解释 C++14 和更高版本的标准,基于 "array" 的特殊解释为参考一些原始数组,可能会以一种更笨拙和冗长但保证有效的方式编写此代码:
// I do not recommend this, but it's one way to avoid problems with some compiler that's
// based on an unreasonable, impractical interpretation of the C++14 standard.
#include <assert.h>
#include <new>
auto main() -> int
{
struct S
{
char v;
};
int const compiler_specific_overhead = 0; // Redefine per compiler.
// With value 0 for the overhead the internal workings here, what happens
// in the machine code, is the same as /without/ this verbose work-around
// for one impractical interpretation of the standard.
int const n = 4;
static_assert( sizeof( S ) == 1, "!" );
char storage[n + compiler_specific_overhead];
S* const a1 = ::new( storage ) S[n];
assert( (void*)a1 == storage + compiler_specific_overhead );
for( int i = 0; i < n; ++i ) { a1[i].v = "a42"[i]; } // Whatever
// Here a2 points to items of the original `char` array, hence no indexing
// UB even with impractical interpretation of the C++14 standard.
// Note that the indexing-UB-free code from this point, is exactly the same
// source code as the first code example that some claim has indexing UB.
char const* const a2 = reinterpret_cast<char const*>( a1 );
for( int i = 0; i < n; ++i )
{
assert( a1[i].v == a2[i] );
}
}
备注:
¹ 该标准保证 struct
的开头没有填充。
如果源数据不变,我想我会倾向于使用编译时转换:
#include <iostream>
#include <array>
struct S
{
char v;
};
namespace detail {
template<std::size_t...Is>
constexpr auto to_cstring(const S* p, std::index_sequence<Is...>)
{
return std::array<char, sizeof...(Is)> {
p[Is].v...
};
}
}
template<std::size_t N>
constexpr auto to_cstring(const S (&arr)[N])
{
return detail::to_cstring(arr, std::make_index_sequence<N>());
}
int main()
{
const /*expr if you wish*/ S a1[] = { {'a'}, {'4'}, {'2'}, {'[=10=]'} };
const /*expr if you wish*/ auto a2 = to_cstring(a1);
for (int i = 0; i < 4; ++i)
std::cout << std::boolalpha << (a1[i].v == a2[i]) << ' ';
}
输出:
true true true true
即使数据不是 constexpr,gcc 和 clang 也非常擅长像这样常量折叠复杂序列。
a2[i]
中的指针算法未定义,参见 C++14 5.7 [expr.add] p7:
For addition or subtraction, if the expressions P
or Q
have type "pointer to cv T
", where T
and the array element type are not similar (4.5), the behavior is undefined. [ Note: In particular, a pointer to a base class cannot be used for pointer arithmetic when the array contains objects of a derived class type. — end note ]
因为这个规则,即使没有填充并且大小匹配,基于类型的别名分析允许编译器假设 a1[i]
和 a2[i]
不重叠(因为指针仅当 a2
确实是 char
的数组时算术才有效,而不仅仅是具有相同大小和对齐方式的数组,并且如果它确实是 char
的数组,则它必须是一个单独的对象S
).
数组
考虑以下类型:
struct S
{
char v;
};
给定一个 const S
的数组,是否有可能 以符合标准的方式,将其重新解释为 const char
的数组,其元素对应到每个原始数组元素的成员 v
的值,反之亦然?例如:
const S a1[] = { {'a'}, {'4'}, {'2'}, {'[=11=]'} };
const char* a2 = reinterpret_cast< const char* >(a1);
for (int i = 0; i < 4; ++i)
std::cout << std::boolalpha << (a1[i].v == a2[i]) << ' ';
上面的代码是可移植的吗?它会打印 true true true true
吗?如果没有,还有其他方法可以实现吗?
显然,可以创建一个新数组并用原数组的每个元素的成员v
初始化它,但整个思路是避免创建一个新数组。
平凡,不 - struct
可能有填充。这完全打破了对数组的任何重新解释。
形式上 struct
可能有填充,因此其大小大于 1。
也就是说,形式上你不能 reinterpret_cast
并且拥有完全可移植的代码,除了¹只有一个项目的数组。
但是对于实践,几年前有人问现在是否有任何编译器默认会为 struct T{ char x; };
提供 sizeof(T) > 1
。我还没有看到任何例子。所以在实践中,可以 static_assert
大小为 1,根本不用担心这个 static_assert
会在某些系统上失败。
即
S const a1[] = { {'a'}, {'4'}, {'2'}, {'[=10=]'} };
static_assert( sizeof( S ) == 1, "!" );
char const* const a2 = reinterpret_cast<char const*>( a1 );
for( int i = 0; i < 4; ++i )
{
assert( a1[i].v == a2[i] );
}
因为 可能 以索引具有未定义行为的方式解释 C++14 和更高版本的标准,基于 "array" 的特殊解释为参考一些原始数组,可能会以一种更笨拙和冗长但保证有效的方式编写此代码:
// I do not recommend this, but it's one way to avoid problems with some compiler that's
// based on an unreasonable, impractical interpretation of the C++14 standard.
#include <assert.h>
#include <new>
auto main() -> int
{
struct S
{
char v;
};
int const compiler_specific_overhead = 0; // Redefine per compiler.
// With value 0 for the overhead the internal workings here, what happens
// in the machine code, is the same as /without/ this verbose work-around
// for one impractical interpretation of the standard.
int const n = 4;
static_assert( sizeof( S ) == 1, "!" );
char storage[n + compiler_specific_overhead];
S* const a1 = ::new( storage ) S[n];
assert( (void*)a1 == storage + compiler_specific_overhead );
for( int i = 0; i < n; ++i ) { a1[i].v = "a42"[i]; } // Whatever
// Here a2 points to items of the original `char` array, hence no indexing
// UB even with impractical interpretation of the C++14 standard.
// Note that the indexing-UB-free code from this point, is exactly the same
// source code as the first code example that some claim has indexing UB.
char const* const a2 = reinterpret_cast<char const*>( a1 );
for( int i = 0; i < n; ++i )
{
assert( a1[i].v == a2[i] );
}
}
备注:
¹ 该标准保证 struct
的开头没有填充。
如果源数据不变,我想我会倾向于使用编译时转换:
#include <iostream>
#include <array>
struct S
{
char v;
};
namespace detail {
template<std::size_t...Is>
constexpr auto to_cstring(const S* p, std::index_sequence<Is...>)
{
return std::array<char, sizeof...(Is)> {
p[Is].v...
};
}
}
template<std::size_t N>
constexpr auto to_cstring(const S (&arr)[N])
{
return detail::to_cstring(arr, std::make_index_sequence<N>());
}
int main()
{
const /*expr if you wish*/ S a1[] = { {'a'}, {'4'}, {'2'}, {'[=10=]'} };
const /*expr if you wish*/ auto a2 = to_cstring(a1);
for (int i = 0; i < 4; ++i)
std::cout << std::boolalpha << (a1[i].v == a2[i]) << ' ';
}
输出:
true true true true
即使数据不是 constexpr,gcc 和 clang 也非常擅长像这样常量折叠复杂序列。
a2[i]
中的指针算法未定义,参见 C++14 5.7 [expr.add] p7:
For addition or subtraction, if the expressions
P
orQ
have type "pointer to cvT
", whereT
and the array element type are not similar (4.5), the behavior is undefined. [ Note: In particular, a pointer to a base class cannot be used for pointer arithmetic when the array contains objects of a derived class type. — end note ]
因为这个规则,即使没有填充并且大小匹配,基于类型的别名分析允许编译器假设 a1[i]
和 a2[i]
不重叠(因为指针仅当 a2
确实是 char
的数组时算术才有效,而不仅仅是具有相同大小和对齐方式的数组,并且如果它确实是 char
的数组,则它必须是一个单独的对象S
).