绕过模板参数整数限制
Bypass template argument integer limit
我正在尝试从模板元编程开始。我写了这样的简单结构:
template<int N>
class Fact {
public:
enum { result = N * Fact<N-1>::result };
};
template<>
class Fact<0> {
public:
enum { result = 1 };
};
当我尝试在 main 中调用它时,如:
int main() {
Fact<5> f;
std::cout << f.result;
}
它适用于较小的参数值。一旦我提供更大的值,比如 15
,编译器就会报错:integer overflow in constant expression
。截断似乎不是由编译器隐式完成的。如果我恢复到较小的值并且我得到要编译的代码,那么我仍然会收到警告:variable set but unused
。为什么?
现在,我对这段代码进行了更多更改,例如:
template<int N>
class Fact {
public:
static long result;
};
template<>
class Fact<0> {
public:
static long result;
};
long Fact<0>::result = 1;
template<int N>
long Fact<N>::result = N * Fact<N-1>::result;
此时警告消失了,而且似乎也允许整数换行。我不明白为什么会这样。我试着写一个香草 class 像:
class X{
public:
static int d;
enum { r = 10000000000000000l };
};
int X::d = 50;
当我使用它时,我仍然收到 unused
警告,枚举也会编译。有人能帮我理解在编译阶段的什么时候,模板推导会发生吗?为什么我能够绕过第一种情况下进行的编译器检查?谢谢!
在您的第一个示例中,Fact<0>::result
的基础类型是 int
。由于您的基本模板的 N
参数也是一个 int
,因此 N * Fact<N-1>::result
的结果也是 int
。这意味着在 N
= 13
处,N * Fact<N-1>::result
的结果将溢出一个 4 字节的 int
。有符号整数溢出是未定义的行为。指定的 enum
值是编译时常量,编译时常量表达式中的未定义行为是不允许的,因此会出现硬错误。
在您的第二个示例中,您不仅使用了 long
,它的范围可能比 int
更大,而且 long Fact<N>::result = N * Fact<N-1>::result;
不是编译时常量表达式。这意味着任何潜在的未定义行为在编译时都不会被检测到。您的程序的行为仍然未定义。
在现代 C++ 中生成编译时阶乘的惯用方法是使用 constexpr
变量而不是未限定范围的 enum
s:
template<unsigned long N>
struct Fact {
static constexpr unsigned long result = N * Fact<N-1>::result;
};
template<>
struct Fact<0> {
static constexpr unsigned long result = 1;
};
或constexpr
函数:
constexpr unsigned long fact(unsigned long n)
{
unsigned long ret = 1;
for (unsigned long i = 1; i <= n; ++i) {
ret *= i;
}
return ret;
}
为了在溢出时保持行为定义明确,我让 result
成员未签名。
如果您真的需要保持与旧编译器的兼容性,您可以将另一个成员添加到您的无范围 enum
中,该成员足够大以强制其基础类型为 unsigned long
:
template<unsigned long N>
struct Fact {
enum {
result = N * Fact<N-1>::result,
placeholder = ULONG_MAX
};
};
template<>
struct Fact<0> {
enum {
result = 1,
placeholder = ULONG_MAX
};
};
请注意,我使用 ULONG_MAX
而不是 std::numeric_limits<unsigned long>::max()
,因为后者只是 C++11 或更高版本中的编译时常量表达式,也是我能想到使用的唯一原因这种方法是为了保持与 C++11 之前的编译器的兼容性。
您可以使用类型 long long.
而不是 int 或 long
class Fact {
public:
enum: long long { result = N * Fact<N - 1>::result };
};
template<>
class Fact<0> {
public:
enum : long long { result = 1 };
};
但是连long long都可以溢出。在汇编程序中有一个溢出标志。但在 C++ 中你可以使用一个技巧,如果结果小于初始值,则在加法或乘法过程中发生溢出。
我正在尝试从模板元编程开始。我写了这样的简单结构:
template<int N>
class Fact {
public:
enum { result = N * Fact<N-1>::result };
};
template<>
class Fact<0> {
public:
enum { result = 1 };
};
当我尝试在 main 中调用它时,如:
int main() {
Fact<5> f;
std::cout << f.result;
}
它适用于较小的参数值。一旦我提供更大的值,比如 15
,编译器就会报错:integer overflow in constant expression
。截断似乎不是由编译器隐式完成的。如果我恢复到较小的值并且我得到要编译的代码,那么我仍然会收到警告:variable set but unused
。为什么?
现在,我对这段代码进行了更多更改,例如:
template<int N>
class Fact {
public:
static long result;
};
template<>
class Fact<0> {
public:
static long result;
};
long Fact<0>::result = 1;
template<int N>
long Fact<N>::result = N * Fact<N-1>::result;
此时警告消失了,而且似乎也允许整数换行。我不明白为什么会这样。我试着写一个香草 class 像:
class X{
public:
static int d;
enum { r = 10000000000000000l };
};
int X::d = 50;
当我使用它时,我仍然收到 unused
警告,枚举也会编译。有人能帮我理解在编译阶段的什么时候,模板推导会发生吗?为什么我能够绕过第一种情况下进行的编译器检查?谢谢!
在您的第一个示例中,Fact<0>::result
的基础类型是 int
。由于您的基本模板的 N
参数也是一个 int
,因此 N * Fact<N-1>::result
的结果也是 int
。这意味着在 N
= 13
处,N * Fact<N-1>::result
的结果将溢出一个 4 字节的 int
。有符号整数溢出是未定义的行为。指定的 enum
值是编译时常量,编译时常量表达式中的未定义行为是不允许的,因此会出现硬错误。
在您的第二个示例中,您不仅使用了 long
,它的范围可能比 int
更大,而且 long Fact<N>::result = N * Fact<N-1>::result;
不是编译时常量表达式。这意味着任何潜在的未定义行为在编译时都不会被检测到。您的程序的行为仍然未定义。
在现代 C++ 中生成编译时阶乘的惯用方法是使用 constexpr
变量而不是未限定范围的 enum
s:
template<unsigned long N>
struct Fact {
static constexpr unsigned long result = N * Fact<N-1>::result;
};
template<>
struct Fact<0> {
static constexpr unsigned long result = 1;
};
或constexpr
函数:
constexpr unsigned long fact(unsigned long n)
{
unsigned long ret = 1;
for (unsigned long i = 1; i <= n; ++i) {
ret *= i;
}
return ret;
}
为了在溢出时保持行为定义明确,我让 result
成员未签名。
如果您真的需要保持与旧编译器的兼容性,您可以将另一个成员添加到您的无范围 enum
中,该成员足够大以强制其基础类型为 unsigned long
:
template<unsigned long N>
struct Fact {
enum {
result = N * Fact<N-1>::result,
placeholder = ULONG_MAX
};
};
template<>
struct Fact<0> {
enum {
result = 1,
placeholder = ULONG_MAX
};
};
请注意,我使用 ULONG_MAX
而不是 std::numeric_limits<unsigned long>::max()
,因为后者只是 C++11 或更高版本中的编译时常量表达式,也是我能想到使用的唯一原因这种方法是为了保持与 C++11 之前的编译器的兼容性。
您可以使用类型 long long.
而不是 int 或 longclass Fact {
public:
enum: long long { result = N * Fact<N - 1>::result };
};
template<>
class Fact<0> {
public:
enum : long long { result = 1 };
};
但是连long long都可以溢出。在汇编程序中有一个溢出标志。但在 C++ 中你可以使用一个技巧,如果结果小于初始值,则在加法或乘法过程中发生溢出。