自定义类似std::byte的类型的别名问题
我正在一个环境中工作,该环境无法访问编译器的常规标准库,主要是由于禁用了异常处理,而改用一个自定义实现来提供同样的功能。例如,我们不会使用 std::vector<std::unique_ptr<T>>,而是使用 my_std::vector<my_std::unique_ptr<T>>,两者的API大致相同。
在引入 my_std::byte 之后,出现了一个担忧:它可能导致未定义行为,因为在生命周期与别名的上下文中,std::byte 被称为一种特殊类型。
据我所理解,这是因为 std::byte* 与 unsigned char* 被明确允许对任意对象进行别名,例如将平凡的类作为二进制缓冲区传递,但 my_std::byte* 将不会。
实现与cppreference上所示相同:
namespace my_std {
enum class byte : unsigned char {};
// bitwise overloads
...
} // namespace my_std
这些担忧成立吗?有哪些具体的代码示例,在使用 std::byte 时有效但在 my_std::byte 时无效?是否有编译器提供类似 #pragma can_alias_anything 的机制,让编译器把我们的类型视作同样的处理?
解决方案
一些编译器可能只是检查该类型是否是在名为 byte 的 std 命名空间中声明的枚举,例如 https://github.com/llvm/llvm-project/blob/98e26bcd03d680c9525aaff9132a543f5bd8dc51/clang/lib/AST/Type.cpp#L3311。
GCC和 Clang都支持 [may_alias] 属性,赋予类似 std::byte/unsigned char 的效果(即“可能对任意类型的任何对象进行别名”),可用如下方式:
namespace my_std {
enum class [[gnu::may_alias]] byte : unsigned char {};
}
区别示例: https://godbolt.org/z/jPbq7EPTe
#include <cstddef>
namespace my_std {
enum class [[gnu::may_alias]] byte : unsigned char {};
enum class nonaliasing_byte : unsigned char {};
}
template<typename B>
bool f(int& x, B& b) {
int read = x;
b = {};
return x == read;
}
template bool f(int&, std::byte&); // Compiles to: read, write, read, compare
template bool f(int&, my_std::byte&); // Compiles to: read, write, read, compare
template bool f(int&, my_std::nonaliasing_byte&); // Compiles to: write, return true
MSVC未实现严格别名优化,因此该属性会被简单地忽略,这个 byte 类将按预期工作。
如果你不使用GCC、Clang或 MSVC,应该考虑为你的编译器寻找一个在没有异常的情况下也能工作的不同标准库,或“自己实现一个标准库”:
// <cstddef>
#include <stddef.h>
namespace std {
// Consult your compiler for if this is the correct implementation
enum class byte : unsigned char {};
}
另外,std::byte 还具备你们的 my_std::byte 所没有的两项属性:隐式对象创建(my_std::byte arr[N] 不会隐式创建对象)以及不确定/错误值的传播:
https://godbolt.org/z/Mo3rn33nh / [basic.idet]
#include <cstddef>
#include <bit>
namespace my_std {
enum class [[gnu::may_alias]] byte : unsigned char {};
}
template<typename B>
constexpr bool f() {
unsigned char c;
B b = std::bit_cast<B>(c);
return true;
}
static_assert(f<unsigned char>());
static_assert(f<std::byte>());
// static_assert(f<my_std::byte>()); // Fails to compile, the indeterminate value becomes UB
不过你可以使用 unsigned char 来隐式创建对象/得到不确定的值,然后将这些 unsigned char 与 [[may_alias]] my_std::byte 进行别名处理。