编译时位掩码的递归模板

Recursive template for compile-time bit mask

本文关键字:递归 掩码 编译      更新时间:2023-10-16

我正试图使用元编程技术创建一个编译时位掩码,我的想法是创建这样的东西:

unsigned int Mask3 = Mask<2>(); // value = 0x03 = b00000000000000000000000000000011
unsigned int Mask3 = Mask<3>(); // value = 0x07 = b00000000000000000000000000000111
unsigned int Mask3 = Mask<7>(); // value = 0x7F = b00000000000000000000000001111111

我正在尝试的代码是:

template <const unsigned int N> const unsigned int Mask()
{
    if (N <= 1)
    {
        return 1;
    }
    else
    {
        return ((1 << N) | Mask<N - 1>());
    }
}

返回1;

但它导致了成吨的警告:

  • 警告C4554:"<lt;':检查运算符优先级是否存在可能的错误
  • 警告C4293:"<lt;':偏移计数为负数或过大

最后,编译错误:

  • 错误C1202:递归类型或函数依赖关系上下文太复杂

因此,我推断递归性永远不会结束,并落入编译器的无限循环中,但我不明白为什么。

正如已经指出的,您依赖于运行时检查停止编译时递归,这是不起作用的。更重要的是,也许,对于你想要做的事情,你正在定义一个函数,直到你调用它才有价值。所以即使在你停止使用特殊化的递归,仍然有一个嵌套序列函数,这些函数将在运行时调用。

如果想要完整的编译时评估,则必须定义静态数据类模板的成员,因为这是编译时的唯一方式常量可以出现在模板中。类似于:

template <unsigned int N>
struct Mask
{
    static unsigned int const value = (1 << (N - 1)) | Mask<N - 1>::value;
};
template <>
struct Mask<0>
{
    static unsigned int const value = 0;
};

(我也纠正了你弄错的数值。)

当然,你不需要这么复杂的东西。以下内容应该做到这一点:

template <unsigned int N>
struct Mask
{
    static unsigned int const value = (1 << (N + 1)) - 1;
};
template <>
struct Mask<0>
{
    static unsigned int const value = 0;
};

(您仍然需要0的专用化。否则,0表示所有位设置。)

当然,最后:要访问值,您需要编写一些内容如CCD_ 1。(您可能需要将其封装在宏中。)

它不需要递归。这应该很好:

template <const unsigned int N> const unsigned int Mask()
{
    return ((1 << N) - 1);
}

实际上,它甚至不需要成为一个模板。一个(内联的)函数是可以的。

请注意,如果您希望支持N的任何值,特别是N >= sizeof(unsigned int) * CHAR_BIT,您可能希望将其视为特殊情况。

模板是在编译时创建的,但您依赖于运行时行为来停止递归。

例如,如果实例化Mask<2> ,它将使用Mask<1> ,将使用Mask<0>,它将使用Mask&lt-1> 等

运行时检查N是否为<=1,但这在编译时没有帮助。它仍然创建了一个无限序列的函数。

要削弱模板实例化递归,您需要引入一个显式专门化:

template <0> const unsigned int Mask()
{
    return 1;
}

递归永远不会结束,因为编译器试图为两个if分支生成模板实现。因此,当它生成Mask<0>它还生成Mask<0xffffffff>等等

C++11——没有递归或模板:

constexpr unsigned mask(unsigned N) { return unsigned(~(-1<<N)); }

到目前为止,答案只解决了第二个错误(C1202),但您提出的问题不止于此。

警告C4554是由涉及模板参数和<lt;操作人员因此,(1<<N)生成警告。如果N是一个普通的参数,当然不会有任何警告。

非常简单的解决方法是使用(1<<(N))而不是(1<<N),C4554就消失了!