【问题标题】:Automating a class template's size argument based on its type argument根据类型参数自动化类模板的大小参数
【发布时间】:2019-05-13 15:36:59
【问题描述】:

在使用类模板时,有没有办法通过推断其类型来指定特定大小?这是一个伪代码示例来代表我要问的内容。

#include <bitset>
#include <cstdint>

namespace xxx {

#define BYTE  8
#define WORD  16
#define DWORD 32
#define QWORD 64

    typedef std::uint8_t   u8;
    typedef std::uint16_t u16;
    typedef std::uint32_t u32;
    typedef std::uint64_t u64;

    template<typename Type, u16 BitCount>
    struct Register {
    };
}

我不知道我是否可以使用部分专业化或完全专业化,我不知道该怎么做。我想做的是:

  • 如果Type == u8 那么BitCount 自动== BYTE8
  • 如果Type == u16 然后BitCount 自动== WORD16
  • 如果Type == u32 那么BitCount 自动== DWORD32
  • 如果Type == u64 那么BitCount 自动== QWORD64

这样做的原因是,当涉及到上面的类及其成员时,我还没有展示,但这里将展示它的成员之一是std::bitset

template<typename Type, u16 BitCount>
struct Register {
   Type value;
   std::bitset<BitCount> 
};

是的,我知道我可以像这样实例化它们:

void someFunc() {
    Register<u8, 8> r8;
    Register<u16, 16> r16;
}

但我希望能够对它们进行专门化,这样您就不必传入任何参数类型,我希望它们可以通过传入的参数类型推断出来,或者,如果只是通过类型,然后尺寸部分是自动的。这是首选

void someFunc() {
    Register<u8> r8;
} 

// the class would automatically use `8` for its `std::bitset<8>` member.

它的底层类型和大小总是一一对应的。

这种实例化无效:

Register<u32, 64> reg; // invalid type u32 can only be 32...

是否有任何已知的方法可以通过特化、继承、多态等来做到这一点?

【问题讨论】:

  • sizeof有什么问题吗?
  • 为什么要将位数作为模板参数?你不能有类似static constexpr u16 BitCount = CHAR_BIT * sizeof(Type); 的东西吗?
  • @HolyBlackCat std::bitset&lt;8&gt; 将成为Register&lt;u8&gt; 的成员,std::bitset&lt;16&gt; 将成为Register&lt;u16&gt; 的成员等等......这些bitset members are initialized by the T 值`成员。跨度>
  • 所以?如果BitCountstatic constexpr 变量,您可以执行std::bitset&lt;BitCount&gt; foo;。你甚至可以直接std::bitset&lt;CHAR_BIT * sizeof(Type)&gt; foo;

标签: c++ templates c++17 template-specialization template-argument-deduction


【解决方案1】:

你可以使用 usings:

using u8Register = Register<u8, BYTE>;
using u16Register = Register<u16, WORD>;
using u32Register = Register<u32, DWORD>;
using u64Register = Register<u64, QWORD>;

或者,如果这些类型与它们的字节大小是一对一的,你可以使用sizeof

#include <limits.h>
template<typename Type, u16 BitCount = sizeof(Type) * CHAR_BIT>
struct Register {
   Type value;
   std::bitset<BitCount> 
};

或者你可以从Register继承来进行专业化。

【讨论】:

  • sizeof() 在这种情况下不起作用,因为它返回的是一个类型的多少字节而不是多少位。
  • @FrancisCugler 已编辑。
  • @Ah 好吧,看起来更好。使用可能没问题,但我有点喜欢使用默认模板参数的第二个。我只是想知道如何为std::bitset&lt;&gt; 参数生成编译时常量值。
  • "一个类型有多少字节而不是多少位。"由于古埃及人发明了乘法,这不是一个无法克服的问题。一个字节中的位数是已知的。
  • 我很难理解你的意思。 sizeof(type) * CHAR_BIT 是类型的大小(以位为单位)。它是一个 constexpr,因此您可以将其用作模板参数。就是这样。
【解决方案2】:

听起来您实际上并不想要第二个模板参数。您只需要一个模板参数,然后直接从中确定位数:

template <typename Type>
struct Register {
    static constexpr auto Bits = sizeof(Type) * CHAR_BIT;
    // ...
};

或者如果你想稍微概括一下这个逻辑:

template <typename> struct BitCount;
template <> struct BitCount<uint8_t>  : integral_constant<size_t, 8> {};
template <> struct BitCount<uint16_t> : integral_constant<size_t, 16> {};
// ...

template <typename Type>
struct Register {
    static constexpr auto Bits = BitCount<T>::value;
    // ...
};

【讨论】:

    【解决方案3】:

    解决我的问题;他们都对此给出了相似的答案,但我认为 Barry's 更具表现力,因此我必须将他的回答归功于他。

    现在对于 Sombrero,他确实帮助我处理了 using Reg# = Register&lt;T&gt;,所以我不必通过传入它们的类型参数来调用这些模板。

    它基本上归结为:

    static constexpr auto Bits = sizeof(Type) * CHAR_BIT;
    

    我要感谢那些为我提供帮助的人。


    使用来自用户的反馈:BarrySombreroChicken 我能够做到这一点:

    ma​​in.cpp

    #include <iostream>
    #include "Register.h"
    
    int main() {
    using namespace vpc;
    
        u8  valA = 8;
        u16 valB = 16;
        u32 valC = 32;
        u64 valD = 64;
    
        Reg8 r8A(valA);
        Reg8 r8B(valB);
        Reg8 r8C(valC);
        Reg8 r8D(valD);
    
        Reg16 r16A(valA);
        Reg16 r16B(valB);
        Reg16 r16C(valC);
        Reg16 r16D(valD);
    
        Reg32 r32A(valA);
        Reg32 r32B(valB);
        Reg32 r32C(valC);
        Reg32 r32D(valD);
    
        Reg64 r64A(valA);
        Reg64 r64B(valB);
        Reg64 r64C(valC);
        Reg64 r64D(valD);
    
        std::cout << r8A << r8B << r8C << r8D;
        std::cout << r16A << r16B << r16C << r16D;
        std::cout << r32A << r32B << r32C << r32D;
        std::cout << r64A << r64B << r64C << r64D;
    
        return EXIT_SUCCESS;
    }
    

    输出

    Reg8(8)
    hex: 0x08
    bin: 00001000
    
    Reg8(16)
    hex: 0x10
    bin: 00010000
    
    Reg8(32)
    hex: 0x20
    bin: 00100000
    
    Reg8(64)
    hex: 0x40
    bin: 01000000
    
    Reg16(8)
    hex: 0x0008
    bin: 0000000000001000
    
    Reg16(16)
    hex: 0x0010
    bin: 0000000000010000
    
    Reg16(32)
    hex: 0x0020
    bin: 0000000000100000
    
    Reg16(64)
    hex: 0x0040
    bin: 0000000001000000
    
    Reg32(8)
    hex: 0x00000008
    bin: 00000000000000000000000000001000
    
    Reg32(16)
    hex: 0x00000010
    bin: 00000000000000000000000000010000
    
    Reg32(32)
    hex: 0x00000020
    bin: 00000000000000000000000000100000
    
    Reg32(64)
    hex: 0x00000040
    bin: 00000000000000000000000001000000
    
    Reg64(8)
    hex: 0x0000000000000008
    bin: 0000000000000000000000000000000000000000000000000000000000001000
    
    Reg64(16)
    hex: 0x0000000000000010
    bin: 0000000000000000000000000000000000000000000000000000000000010000
    
    Reg64(32)
    hex: 0x0000000000000020
    bin: 0000000000000000000000000000000000000000000000000000000000100000
    
    Reg64(64)
    hex: 0x0000000000000040
    bin: 0000000000000000000000000000000000000000000000000000000001000000
    

    Register.h

    #pragma once
    
    #include <algorithm>
    #include <bitset>
    
    #include <string>
    #include <vector> // include for typedefs below.
    
    namespace vpc {
        typedef std::int8_t  i8;
        typedef std::int16_t i16;
        typedef std::int32_t i32;
        typedef std::int64_t i64;
    
        typedef std::uint8_t u8;
        typedef std::uint16_t u16;
        typedef std::uint32_t u32;
        typedef std::uint64_t u64;
    
        const u16 BYTE = 0x08;
        const u16 WORD = 0x10;
        const u16 DWORD = 0x20;
        const u16 QWORD = 0x40;
    
        typedef std::bitset<BYTE>  Byte;
        typedef std::bitset<WORD>  Word;
        typedef std::bitset<DWORD> DWord;
        typedef std::bitset<QWORD> QWord;
    
        template<typename Ty>
        struct Register_t {
            static constexpr u16 BitCount = sizeof(Ty) * CHAR_BIT;
            Ty currentValue;
            Ty previousValue;
            std::bitset<BitCount> bits;
    
            Register_t() : currentValue{ 0 }, previousValue{ 0 }, bits{ 0 }{}
    
            template<typename U>
            explicit Register_t(U val) : currentValue{ static_cast<Ty>(val) }, previousValue{ 0 }, bits{ currentValue } {}
        };
    
        template<typename Ty>
        struct Register : public Register_t<Ty> {
            Register() = default;
            explicit Register(Ty val) : Register_t<Ty>( val ) {}
        };
    
        using Reg8  = Register<u8>;
        using Reg16 = Register<u16>;
        using Reg32 = Register<u32>;
        using Reg64 = Register<u64>;
    
        std::ostream& operator<<(std::ostream& os, const Reg8&  reg);
        std::ostream& operator<<(std::ostream& os, const Reg16& reg);
        std::ostream& operator<<(std::ostream& os, const Reg32& reg);
        std::ostream& operator<<(std::ostream& os, const Reg64& reg);
    
    } // namespace vpc
    

    Register.cpp

    #include "Register.h"
    
    #include <iostream>
    #include <iomanip>
    
    namespace vpc {
    
        std::ostream& operator<<(std::ostream& os, const Reg8& r) {
            os << "Reg8(" << +r.currentValue << ")\n"
                << "hex: " << "0x" << std::uppercase
                << std::setfill('0') << std::setw(2) << std::hex
                << +r.currentValue << std::dec << '\n'
                << "bin: " << r.bits << '\n' << std::endl;
            return  os;
        }
        std::ostream& operator<<(std::ostream& os, const Reg16& r) {
            os << "Reg16(" << r.currentValue << ")\n"
                << "hex: " << "0x" << std::uppercase
                << std::setfill('0') << std::setw(4) << std::hex
                << r.currentValue << std::dec << '\n'
                << "bin: " << r.bits << '\n' << std::endl;
            return  os;
        }
        std::ostream& operator<<(std::ostream& os, const Reg32& r) {
            os << "Reg32(" << r.currentValue << ")\n"
                << "hex: " << "0x" << std::uppercase
                << std::setfill('0') << std::setw(8) << std::hex
                << r.currentValue << std::dec << '\n'
                << "bin: " << r.bits << '\n' << std::endl;
            return  os;
        }
        std::ostream& operator<<(std::ostream& os, const Reg64& r) {
            os << "Reg64(" << r.currentValue << ")\n"
                << "hex: " << "0x" << std::uppercase
                << std::setfill('0') << std::setw(16) << std::hex
                << r.currentValue << std::dec << '\n'
                << "bin: " << r.bits << '\n' << std::endl;
            return  os;
        }
    
    } // namespace vpc
    

    这就是我要找的。​​p>

    【讨论】:

    • 基本上所有这些代码都归结为决定使用静态变量,正如 Barry 建议的那样。我会接受他的回答。
    • @HolyBlackCat 基本上是的;我盯着代码和数字看了好几个小时……而在我的一生中,我可能已经知道但想不出的这么简单的事情让我很难过……
    • @HolyBlackCat 这些只是“基本构造函数”。我的单元测试项目中有更多的构造函数,其中有 4 个非模板非继承的单独类。我只是想删除很多重复的代码。我有构造函数,它们不仅可以取值,还可以取其他 4 种寄存器中的任何一种。我也有构造函数,它们将采用更大的类型值,或具有 in 索引值的更大类型寄存器,它将检索位模式的一部分并使用该值构造新寄存器。
    • 你为什么在typedefusing 之间摇摆不定?
    • @RyanHaining 这真的重要吗?
    猜你喜欢
    • 1970-01-01
    • 2022-01-20
    • 2014-05-18
    • 2018-05-01
    • 2019-11-10
    • 2014-09-16
    • 2011-09-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多