如何使用类似unsigned char的类型,但不允许使用别名?

问题描述 投票:2回答:2

我想要一个类型,就像unsigned char

  • sizeof是1
  • 可以为其分配整数值(没有任何强制转换)
  • 允许位操作
  • 算术是允许的,但不是必须的
  • 无符号
  • 平凡的可复制

但是,与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

c++ c++17
2个回答
3
投票

该标准的那一部分称为charunsigned charstd::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不会继承与charstd::byte相同的别名属性


0
投票

您可以定义自己的类型:

#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));
© www.soinside.com 2019 - 2024. All rights reserved.