【问题标题】:Odd C++ template behaviour with static member vars带有静态成员变量的奇怪 C++ 模板行为
【发布时间】:2010-06-18 11:16:03
【问题描述】:

这段代码应该在编译时使用以下方法计算 e 的近似值(即数学常数 ~ 2.71828183);

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 的错误?

【问题讨论】:

  • 如果使用 GNU 编译器编译,您的代码 works fine
  • @Rotsor:谢谢,知道这很有用。我已经相应地更新了问题。
  • 你不应该让标识符全部大写,这会让它们看起来像宏。我什至无法识别您的代码。
  • 我更喜欢大写的模板参数。

标签: c++ templates static metaprogramming


【解决方案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

【讨论】:

  • 语言中是否未指定顺序? IE。两种实现都允许吗?
  • 我认为订单未指定。
  • 是的,订单未指定。见this answer
【解决方案2】:

对于它的价值,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

【讨论】:

    【解决方案3】:

    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;
    }
    

    【讨论】:

    • 使用非整数编译时间常数是重点,否则我可以使用我的 f() 函数。
    • 但是你不能确定 f() 确实会在编译时被内联和计算。
    猜你喜欢
    • 2018-08-06
    • 2023-03-18
    • 1970-01-01
    • 2011-03-14
    • 1970-01-01
    • 2017-10-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多