【问题标题】:How to have a type which is like unsigned char, but not allowed to alias?如何拥有类似于 unsigned char 但不允许别名的类型?
【发布时间】:2019-02-13 18:45:07
【问题描述】:

我想要一个类型,比如unsigned char:

  • sizeof 为 1
  • 可以为其分配整数值(无需任何强制转换)
  • 允许位操作
  • 算术是允许的,但不是必须的
  • 未签名
  • 可轻松复制

但是,与unsigned char 不同的是,它不允许别名。我的意思是,一个类型,它没有异常[basic.lval/11.8]

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

[...]

  • char、unsigned char 或 std​::​byte 类型。

有可能有这样的类型吗?

原因:我几乎从不使用unsigned char 的别名属性。所以,我想改用一个类型,它不会阻止某些类型的优化(注意,我问这个问题是因为我实际上有一些函数,由于 @ 的别名允许属性,这些函数没有得到很好的优化987654327@)。所以,我想有一个这样的类型:“不要为你不使用的东西买单”。


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

【问题讨论】:

  • static_assert std::uint8_t 可用。
  • @RichardCritten std::uint8_t 不会有太大帮助,因为大多数实现 typedef 它是unsigned char。 // 编辑:stackoverflow.com/questions/16138237/…
  • 为什么整数值可以分配给它没有强制转换如此重要,以至于您可以编辑问题以响应需要它的解决方案?我想不出为什么写 some_type{n} 而不是 n 会破坏交易
  • @Justin:“可以为它分配整数值”。对我来说,它已经意味着a = 42;,没有演员表。对于您的解决方案,“可以为其分配整数值”是不正确的。你转换它,所以你分配一个枚举,而不是一个整数。我已经编辑了这个问题,以明确这一点,而不是抹黑你的答案。
  • 您可以使用几个#ifdefrestrict__restrict__restrict__

标签: c++ c++17


【解决方案1】:

您可以定义自己的类型:

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

【讨论】:

  • @Ivan 这是故意的。请注意operator unsigned char()explicit。您必须使用static_cast&lt;unsigned char&gt;(uchar()) 明确要求它。如果你想要一个隐式转换,你可以删除 explicit
  • 然后int() ? int() : uchar() 坏了。
  • @Ivan 这就是为什么我不推荐双向隐式转换
  • 请注意,无法保证此类型具有unsigned char 的大小/对齐方式。你可以在上面static_assert,但这是你能做的最好的。
  • @NicolBolas 非常正确。但是,如果那些static_asserts 失败了,我会感到非常惊讶。我无法想象任何实现会为单成员类型添加填充。
【解决方案2】:

标准的该部分调用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。如果不允许 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_byte 不继承与charstd::byte 相同的别名属性

【讨论】:

  • 谢谢,这种方法的问题是它不方便地支持整数赋值(需要强制转换),我看不出有什么办法可以解决这个问题。
  • @geza 你可以写my_byte{n},让它看起来像任何其他类型没有隐式转换。
  • 我的意思是,我想写a = 42;,就像我写unsigned char一样。没有演员表。
  • @geza 我想不出一种方法来完成这项工作,这真的没什么大不了的。缺少隐式转换并不是一件很痛苦的事,尤其是因为您不必写static_cast&lt;my_byte&gt;(n)。另外,我认为从技术上讲,它不被视为演员表。你可以为它添加一个文字,比如42_byte
  • 我打算在我的代码库中用这个假设的类型替换unsigned char。这将意味着很多额外的演员阵容。这是噪音,我不喜欢它。
猜你喜欢
  • 2015-01-03
  • 2018-10-13
  • 1970-01-01
  • 2022-10-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-19
相关资源
最近更新 更多