代码之家  ›  专栏  ›  技术社区  ›  geza

如何拥有一个类似于无符号字符但不允许别名的类型?

  •  2
  • geza  · 技术社区  · 6 年前

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

    • sizeof为1
    • 可以为其指定整数值(无任何强制转换)
    • 允许位操作
    • 算术是允许的,但不是必须的
    • 未签名
    • 微不足道的可复制

    但是,不像 ,不允许别名。我是说,一种类型,没有例外 [basic.lval/11.8]

    如果程序试图通过glvalue(不是以下类型之一)访问对象的存储值,则行为未定义:

    [...]

    • 一个字符, 或std::字节类型。

    有可能有这样的类型吗?

    无符号字符 的别名属性。所以,我想改为使用一个类型,它不会阻止某些类型的优化(注意,我问这个问题是因为我实际上有一些函数,它们没有很好地优化,因为 无符号字符 ). 所以,我想有一个类型,这是真的:“不要为你不用的东西付钱”。


    下面是一个例子,其中 阻止优化: Using this pointer causes strange deoptimization in hot loop

    2 回复  |  直到 6 年前
        1
  •  3
  •   Justin    6 年前

    标准的那一节 char , unsigned char ,和 std::byte . 但是,你可以做你自己的类型 也不允许用别名:

    enum class my_byte : unsigned char {};
    

    无符号字符 做任何有意义的事。但是,您可以重载位运算符和算术运算符,使其更易于使用。


    我们可以用下面的简单函数来验证这一点:

    auto foo(A& a, B& b) {
        auto lhs = b;
        a = 42;
        auto rhs = b;
        return lhs + rhs;
    }
    

    如果 A 被允许与 B ,编译器将必须生成两个加载:一个用于 lhs rhs . 如果 不允许与别名 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 与继承的别名属性不同 烧焦 标准::字节

        2
  •  0
  •   Justin    6 年前

    您可以定义自己的类型:

    #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));