递归模板用于编译时位掩码。

3

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

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:“'<<':检查可能出错的运算符优先级”
  • 警告 C4293:“'<<':移位计数为负或太大”

最终编译错误如下:

  • 错误 C1202:“递归类型或函数依赖上下文过于复杂”。

因此,我推断递归从未结束,进入了编译器的无限循环,但我不明白为什么会这样。


为什么不这样做呢:“int result = 0; for(i=0; i<N; i++){ result = result << 1; result += 1; } return result;”? - SinisterMJ
6个回答

4

不需要使用递归。这种方法应该可以正常工作:

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

其实不一定非得是模板,一个内联函数也可以。

需要注意的是,如果你希望支持任何值的N,特别是N >= sizeof(unsigned int) * CHAR_BIT,你可能需要把它们视为特殊情况。


4
正如已经指出的那样,您依靠运行时检查来停止编译时递归,这是不起作用的。更重要的是,也许对于您想要做的事情,您正在定义一个函数,该函数在调用之前没有价值。因此,即使您使用专业化停止递归,仍然会有嵌套的函数序列将在运行时调用。
如果您想进行完全的编译时评估,则必须定义类模板的静态数据成员,因为这是编译时常量可以出现在模板中的唯一方式。例如:
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表示所有位都设置。)

最后,当然:要访问该值,您需要编写类似于Mask<3>::value的内容。(您可能需要将其包装在宏中。)


2
除了Sander De Dycker的代码仍然涉及函数调用,因此该值不是常量。(但是,使用C++11,它可以声明为“constexpr”)。并且它在N == 0时失败。(否则,它对应于我上面的第二个解决方案。) - James Kanze
如果N >= 32,我的编译器会抱怨它不符合标准,即使我将其设置为“unsigned long long”。命令:**clang++-3.3 -Wall -Werror -std=c++11 -pedantic Mask.cpp**(标志标准为严格的C++11)。结果:Mask.cpp:7:48: error: in-class initializer for static data member is not a constant expression; folding it to a constant is a GNU extension [-Werror,-Wgnu] static unsigned long long const value = (1 << (N + 1)) - 1; Mask.cpp:19:10: note: in instantiation of template class 'Mask<32>' requested here cout << Mask<32>::value << endl; - Siu Ching Pong -Asuka Kenji-
@AsukaKenji-SiuChingPong- 是的。没有负面的字面量;-42是一个由两个标记组成的序列。这是C++的设计方式,在二进制补码架构(几乎所有现代计算机都是如此)上,将会有一个负值无法被表示为字面量。但你在一个点上是错误的,-9223372036854775808ULL不会得到一个负数,它的类型仍然是unsigned long long,值为9223372036854775808ULL。如果long long是64位,则将该值转换为long long的结果是实现定义的。 - James Kanze
是的,你说得对!我忽略了字面值 1 的类型。现在我的代码编译没有任何问题。我的实现允许用户指定掩码定义的(整数)类型,所以我必须写 **static T const value = (static_cast<T>(1) << N) - static_cast<T>(1);**,其中 T 是整数类型的类型参数。 - Siu Ching Pong -Asuka Kenji-
对于第二个问题,我尝试使用 static std::int64_t const my_min_1 = 9223372036854775808ULL; 和 **static std::int64_t const my_min_2 = -9223372036854775808ULL;**,但都没有发出编译器警告。我没有查阅标准,但由于该值存储在 int64_t 中,它不能是 9223372036854775808 - 数据类型不足以存储它。而且,由于机器使用的是二进制补码,可以合理地说二进制值 1000...(63 0's) 变成了 -9223372036854775808。在 static std::int32_t const my_min_3 = 2147483648; 中也观察到了相同的行为。 - Siu Ching Pong -Asuka Kenji-
显示剩余6条评论

3
为了避免模板实例化递归,您需要引入一个明确的专门化。
template <0> const unsigned int Mask()
{
    return 1;
}

您的递归永远不会停止,因为编译器尝试为两个if分支生成模板实现。因此,在它生成Mask<0>时,它也会生成Mask<0xffffffff>等。


3

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

例如,如果您实例化Mask<2>,它将使用Mask<1>,而Mask<1>将使用Mask<0>,Mask<0>将使用Mask<-1>,等等。

您对N进行了运行时检查,判断其是否<= 1,但这在编译时并没有帮助。它仍会创建无限序列的函数。


2

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

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

1
到目前为止,答案只解决了第二个错误(C1202),但您需要的不仅如此。
警告C4554是由于Microsoft编译器中涉及模板参数和<<操作符的错误引起的。因此,(1 << N)会生成警告。如果N是普通参数,则当然不会有警告。
非常简单的解决方法是使用(1 << (N))而不是(1 << N),这样C4554就消失了!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接