我想要一个类型,就像unsigned char
:
但是,与unsigned char
不同,它不允许别名。我的意思是,一个类型,没有例外[basic.lval/11.8]:
如果程序试图通过以下类型之一以外的glvalue访问对象的存储值,则行为未定义:
[...]
- char,unsigned char或std :: byte类型。
是否有可能有这样的类型?
原因是:我几乎从不使用unsigned char
的别名属性。所以,我想使用一种类型,它不会阻止某种优化(注意,我问这个问题,因为我实际上有一些功能,由于unsigned char
的别名允许属性,因此没有很好地优化) )。所以,我想要一个这样的类型:“不要为你不使用的东西买单”。
这是一个例子,unsigned char
阻止优化:Using this pointer causes strange deoptimization in hot loop
该标准的那一部分称为char
,unsigned char
和std::byte
。但是,您可以创建自己的类型,如std::byte
,并且不允许使用别名:
enum class my_byte : unsigned char {};
使用它并不是那么好,因为你必须投射到unsigned char
做任何有意义的事情。但是,您可以重载按位和算术运算符,以使其更好用。
我们可以使用以下简单函数验证这一点:
auto foo(A& a, B& b) {
auto lhs = b;
a = 42;
auto rhs = b;
return lhs + rhs;
}
如果A
被允许使用B
别名,则编译器必须生成两个加载:一个用于lhs
,一个用于rhs
。如果A
不允许使用B
进行别名,则编译器可以生成单个加载并将值添加到自身。 Let's test it:
// int& cannot alias with long&
auto foo(int& a, long& b) {
auto lhs = b;
a = 42;
auto rhs = b;
return lhs + rhs;
}
// std::byte& can alias with long&
auto bar(std::byte& a, long& b) {
auto lhs = b;
a = (std::byte)42;
auto rhs = b;
return lhs + rhs;
}
// if my_byte& can alias with long&, there would have to be two loads
auto baz(my_byte& a, long& b) {
auto lhs = b;
a = (my_byte)42;
auto rhs = b;
return lhs + rhs;
}
这导致以下结果:
foo(int&, long&):
mov rax, QWORD PTR [rsi]
mov DWORD PTR [rdi], 42
add rax, rax
ret
bar(std::byte&, long&):
mov rax, QWORD PTR [rsi]
mov BYTE PTR [rdi], 42
add rax, QWORD PTR [rsi]
ret
baz(my_byte&, long&):
mov rax, QWORD PTR [rsi]
mov BYTE PTR [rdi], 42
add rax, rax
ret
因此my_byte
不会继承与char
和std::byte
相同的别名属性
您可以定义自己的类型:
#include <type_traits>
class uchar {
unsigned char value = {};
public:
template <typename T,
std::enable_if_t<
std::is_convertible_v<T, unsigned char>,
int
> = 0>
constexpr uchar(T value)
: value{static_cast<unsigned char>(value)}
{}
constexpr uchar()
{}
template <typename T,
std::enable_if_t<
std::is_convertible_v<T, unsigned char>,
int
> = 0>
constexpr uchar& operator=(T value)
{
this->value = static_cast<unsigned char>(value);
return *this;
}
explicit constexpr operator unsigned char() const
{
return value;
}
friend constexpr uchar operator+(uchar lhs, uchar rhs) {
return lhs.value + rhs.value;
}
friend constexpr uchar operator-(uchar lhs, uchar rhs) {
return lhs.value - rhs.value;
}
// And so on...
};
// The compiler could technically add padding after the `value` member of
// `uchar`, so we `static_assert` to verify that it didn't. I can't imagine
// any sane implementation would do so for a single-member type like `uchar`
static_assert(sizeof(uchar) == sizeof(unsigned char));
static_assert(alignof(uchar) == alignof(unsigned char));