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

时间:2018-09-08 20:12:50

标签: c++ c++17

我想要一种类型,例如unsigned char

  • sizeof为1
  • 可以将整数值分配给它(不强制转换)
  • 允许位操作
  • 允许算术运算,但不是必须的
  • 未签名
  • 普通复制

但是,与unsigned char不同,不允许使用别名。我的意思是,该类型没有[basic.lval/11.8]例外:

  

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

     

[...]

     
      
  • 一种字符,无符号字符或std ::字节类型。
  •   

是否可以具有这样的类型?

原因:我几乎从不使用unsigned char的别名属性。因此,我想改用一种类型,该类型不会阻止某些类型的优化(注意,我问这个问题是因为我实际上有一些函数,由于{ {1}})。因此,我希望有一种类型适用于此类型:“不用为不使用的东西付费”。


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

2 个答案:

答案 0 :(得分: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;
}

如果允许AB的别名,则编译器将不得不生成两个负载:一个负载为lhs,另一个负载为rhs。如果不允许AB的别名,则编译器可以生成单个加载并将其自身添加值。 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_bytecharstd::byte继承的别名属性不同

答案 1 :(得分: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));