【问题标题】:Overloading a template based on size of type根据类型的大小重载模板
【发布时间】:2021-05-07 01:28:58
【问题描述】:

我正在尝试编写一个模板化函数,该函数将允许我传入一个显式大小的枚举,它会根据 C++ 11 中枚举的大小调用正确的序列化函数。这是我目前拥有的,工作正常:

template<typename EnumName>
uint32_t WriteBuffer::Write_enum(EnumName aValue)
{
    constexpr uint32_t cNumBits = sizeof(EnumName) * 8;
    switch(cNumBits)
    {
        case 8:
        {
            return Write_uint8(static_cast<uint8_t>(aValue));
        }
        break;
        case 16:
        {
            return Write_uint16(static_cast<uint16_t>(aValue));
        }
        break;
        case 64:
        {
            return Write_uint64(static_cast<uint64_t>(aValue));
        }
        break;
        case 32:
        default:
        {
            return Write_uint32(static_cast<uint32_t>(aValue));
        }
        break;
    }
}

enum MyEnum : uint8_t
{
    A,
    B
}

void SomeFunction()
{
    m_writeBuffer.Write_enum(m_myEnum);
}

但是,这仍然需要一些运行时工作来运行开关。我很好奇模板是否可用于确定在编译时调用的正确内容,因为枚举的大小在编译时是已知的。像这样的东西,虽然这显然不起作用:

template<typename EnumName>
uint32_t WriteBuffer::Write_enum(EnumName aValue)
{
    constexpr uint32_t cNumBits = sizeof(EnumName) * 8;
    Write_sizedenum<EnumName, cNumBits>(aValue);
}

template<typename EnumName, 8>
uint32_t WriteBuffer::Write_sizedenum(EnumName aValue)
{
    return Write_uint8(static_cast<uint8_t>(aValue));
}

template<typename EnumName, 16>
uint32_t WriteBuffer::Write_sizedenum(EnumName aValue)
{
    return Write_uint16(static_cast<uint16_t>(aValue));
}

// etc

请注意,出于代码生成的原因,我不知道编写调用 Write_enum 的代码的枚举的大小,因此我需要重载和模板的某种组合,这样我就可以使用一个名称调用一个函数适用于所有枚举大小。

这可能与模板有关吗?还是运行时切换是我能做的最好的?

【问题讨论】:

  • 代码生成与否,当您可以将EnumName 类型的值传递给函数时,您也可以传递sizeof(EnumName)。不过,最好在编译时就使用该信息。
  • 由于cNumBitsconstexpr,您可以进行一系列if constexpr (cNumBits == 8)else if constexpr (cNumBits == 16) 等检查,而不是switch,这将在编译时进行评估.

标签: c++ templates


【解决方案1】:

对于这种非常特殊的情况,您可以使用std::underlying_type 提取类型名称,然后依赖普通的旧重载:

struct WriteBuffer
{
    template<typename EnumName>
    uint32_t Write_enum(EnumName aValue) {
        Write_overloaded(static_cast<typename std::make_unsigned<typename std::underlying_type<EnumName>::type>::type>(aValue));
    }

    uint32_t Write_overloaded(uint8_t);
    uint32_t Write_overloaded(uint16_t);
    uint32_t Write_overloaded(uint32_t);
    uint32_t Write_overloaded(uint64_t);
};

更一般地,您可以使用标签调度,使用std::integral_constant 来进行调度。大多数编译器会很高兴地省略结构实例并将其简化为一个简单的函数调用:

struct WriteBuffer
{
    template<typename EnumName>
    uint32_t Write_enum(EnumName aValue) {
        Write_enum(aValue, std::integral_constant<std::size_t, (sizeof(EnumName) * 8)> {});
    }

    template<typename EnumName>
    uint32_t Write_enum(EnumName aValue, std::integral_constant<std::size_t, 8>);
    template<typename EnumName>
    uint32_t Write_enum(EnumName aValue, std::integral_constant<std::size_t, 16>);
    template<typename EnumName>
    uint32_t Write_enum(EnumName aValue, std::integral_constant<std::size_t, 32>);
    template<typename EnumName>
    uint32_t Write_enum(EnumName aValue, std::integral_constant<std::size_t, 64>);
};

【讨论】:

    【解决方案2】:

    您可以简单地创建一个if constexpr-else 语句链,以便创建与您的switch 等效的内容,但要保证编译时决策。

    template<typename EnumName>
    uint32_t WriteBuffer::Write_enum(EnumName aValue)
    {
        constexpr uint32_t bits = sizeof(EnumName) * 8;
        if constexpr (bits == 8) {
            return Write_uint8(static_cast<uint8_t>(aValue));
        }
        else if constexpr (bits == 16) {
            return Write_uint16(static_cast<uint16_t>(aValue));
        }
        else if constexpr (bits == 32) {
            return Write_uint32(static_cast<uint32_t>(aValue));
        }
        else if constexpr (bits == 64) {
            return Write_uint64(static_cast<uint64_t>(aValue));
        }
        else {
            // This condition is always false, but if we used
            // static_assert(false), it would always fail to compile.
            // We must create a dependency on the template parameter.
            static_assert(!bits, "Error, unexpected size!");
        }
    }
    
    enum MyEnum : uint8_t
    {
        A,
        B
    };
    
    void SomeFunction()
    {
        m_writeBuffer.Write_enum(m_myEnum);
    }
    

    就个人而言,我什至不会为这种重载而烦恼,只需添加一个写入原始字节的函数,然后将任何其他类型转换为字节。

    uint32_t WriteBuffer::Write_bytes(std::byte arr[], std::size_t size) {
        // TODO: implement
    }
    
    template<typename EnumName>
    uint32_t WriteBuffer::Write_enum(EnumName aValue)
    {
        std::byte buffer[sizeof(EnumName)];
        std::memcpy(buffer, &aValue, sizeof(EnumName));
        return Write_bytes(buffer, sizeof(EnumName));
    }
    

    这会容易得多。如果我们关心处理字节顺序,我们可以在不匹配的情况下执行我们类型的条件字节反转。

    template<std::endian Endian, typename T>
    T fix_endian(T value)
    {
        if constexpr (Endian == std::endian::native) {
            return value;
        }
        else {
            auto integer = __builtin_bit_cast(/* equally sized uint type */, value);
            // TODO: wrap builtins in portable wrapper
            //       bswap is same for GCC and clang
            integer = __builtin_bswap(integer);
            return __builtin_bit_cast(T, integer);
        }
    }
    

    这种字节序处理可以说不再那么容易了,但它普遍适用于所有类型,包括浮点类型和枚举。

    【讨论】:

    • 我喜欢 if constexpr,不幸的是那是 C++17。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多