2010-06-18 52 views
2

这段代码应该使用以下方法在编译时计算近似值e(即数学常数〜2.71828183);使用静态成员变量的奇数C++模板行为

e1 = 2/1 
e2 = (2 * 2 + 1)/(2 * 1) = 5/2 = 2.5 
e3 = (3 * 5 + 1)/(3 * 2) = 16/6 ~ 2.67 
e4 = (4 * 16 + 1)/(4 * 6) = 65/24 ~ 2.708 
... 
e(i) = (e(i-1).numer * i + 1)/(e(i-1).denom * i) 

计算经由result静态成员然而返回,经过2次迭代它产生零,而不是预期的值。我已经添加了一个静态成员函数f()来计算相同的值,并且不会出现相同的问题。

#include <iostream> 
#include <iomanip> 

// Recursive case. 

template<int Iters, int Num = 2, int Den = 1, int I = 2> 
struct CalcE 
{ 
    static const double result; 
    static double f() {return CalcE<Iters, Num * I + 1, Den * I, I + 1>::f();} 
}; 

template<int Iters, int Num, int Den, int I> 
const double CalcE<Iters, Num, Den, I>::result = CalcE<Iters, Num * I + 1, Den * I, I + 1>::result; 

// Base case. 

template<int Iters, int Num, int Den> 
struct CalcE<Iters, Num, Den, Iters> 
{ 
    static const double result; 
    static double f() {return result;} 
}; 

template<int Iters, int Num, int Den> 
const double CalcE<Iters, Num, Den, Iters>::result = static_cast<double>(Num)/Den; 
// Test it. 

int main (int argc, char* argv[]) 
{ 
    std::cout << std::setprecision (8); 

    std::cout << "e2 ~ " << CalcE<2>::result << std::endl; 
    std::cout << "e3 ~ " << CalcE<3>::result << std::endl; 
    std::cout << "e4 ~ " << CalcE<4>::result << std::endl; 
    std::cout << "e5 ~ " << CalcE<5>::result << std::endl; 

    std::cout << std::endl; 
    std::cout << "e2 ~ " << CalcE<2>::f() << std::endl; 
    std::cout << "e3 ~ " << CalcE<3>::f() << std::endl; 
    std::cout << "e4 ~ " << CalcE<4>::f() << std::endl; 
    std::cout << "e5 ~ " << CalcE<5>::f() << std::endl; 

    return 0; 
} 

我在VS 2008和VS 2010进行了测试,得到在每种情况下相同的结果:

e2 ~ 2 
e3 ~ 2.5 
e4 ~ 0 
e5 ~ 0 

e2 ~ 2 
e3 ~ 2.5 
e4 ~ 2.6666667 
e5 ~ 2.7083333 

为什么result没有取得预期值,而f()呢?

根据Rotsor的评论下面,这与GCC工作,所以我想问题是,我依赖于静态初始化顺序的某种类型的未定义的行为,或者这是Visual Studio的错误?

+1

如果使用GNU编译器编译,您的代码[works fine](http://codepad.org/TqziZEMi)。 – Rotsor 2010-06-18 11:25:50

+0

@Rotsor:谢谢,这很有用。我已经更新了相应的问题。 – 2010-06-18 11:39:51

+0

你不应该使标识符全部大写,它使它们看起来像宏。我甚至无法开始识别你的代码。 – Puppy 2010-06-18 11:45:10

回答

1

显然,你不能依赖静态成员初始化的顺序上,至少在VC++。

下面是一个简化的例子:

#include <stdio.h> 

template<int N> 
struct one 
{ 
    static const int res; 
}; 

template<> 
struct one<0> 
{ 
    static const int res; 
}; 

template<int N> 
const int one<N>::res = one<N-1>::res; 

const int one<0>::res = 1; 

int main() 
{ 
    printf("%d\n", one<3>::res); 
    printf("%d\n", one<2>::res); 
    printf("%d\n", one<1>::res); 
    printf("%d\n", one<0>::res); 
} 

在VC++ 2008,它产生:

0 
1 
1 
1 

codepad,其产生:

1 
1 
1 
1 
+0

该语言中的订单是否未指定?即是否允许实现? – 2010-06-18 14:25:26

+0

我认为订单是未指定的。 – Amnon 2010-06-18 14:54:57

+0

是的,订单是未指定的。看到[这个答案](http://stackoverflow.com/questions/1819131/c-static-member-initalization-template-fun-inside/1825872#1825872) – 2010-06-18 17:53:45

0

对于它的价值,结果与G ++ 4.4.1:

e2 ~ 2 
e3 ~ 2.5 
e4 ~ 2.6666667 
e5 ~ 2.7083333 

e2 ~ 2 
e3 ~ 2.5 
e4 ~ 2.6666667 
e5 ~ 2.7083333 
0

C++不喜欢非积分编译时间常量。一个可能的解决方案是使用有理算术:

#include <iostream> 
#include <iomanip> 

template<int Iters, int Num = 2, int Den = 1, int I = 2> 
struct CalcE 
{ 
    typedef CalcE<Iters, Num * I + 1, Den * I, I + 1> res; 
    enum { num = res::num, den = res::den }; 
    static double g() { return static_cast<double>(num)/den; } 
}; 

template<int Iters, int Num, int Den> 
struct CalcE<Iters, Num, Den, Iters> 
{ 
    enum { num = Num, den = Den }; 
    static double g() { return static_cast<double>(num)/den; } 
}; 

int main (int argc, char* argv[]) 
{ 
    std::cout << std::setprecision (8); 

    std::cout << "e2 ~ " << CalcE<2>::g() << std::endl; 
    std::cout << "e3 ~ " << CalcE<3>::g() << std::endl; 
    std::cout << "e4 ~ " << CalcE<4>::g() << std::endl; 
    std::cout << "e5 ~ " << CalcE<5>::g() << std::endl; 
    std::cout << "e5 ~ " << CalcE<6>::g() << std::endl; 

    return 0; 
} 
+0

使用非积分编译时间常量是点,否则我可以使用我的f()函数。 – 2010-06-18 15:06:54

+0

但是你不能确定f()确实会在编译时被内联和计算。 – Amnon 2010-06-18 15:13:39