递归模板与静态变量一起使用时无法如预期般工作

6

代码

#include <iostream>
using namespace std;

template<int n> struct Fibo { static int x; };
template<> int Fibo<0>::x = 1;
template<> int Fibo<1>::x = 1;
template<int n> int Fibo<n>::x = Fibo<n-1>::x + Fibo<n-2>::x; //marked line

int main() {
    cout << Fibo<5>::x << endl;
    cout << Fibo<4>::x << endl;
    cout << Fibo<3>::x << endl;
    cout << Fibo<2>::x << endl;
    cout << Fibo<1>::x << endl;
    cout << Fibo<0>::x << endl;

    return 0;
}

输出

0
0
1
2
1
1

在VC++中(根据用户M M.的说法,它在gcc中可以正常编译)。当编译器到达带有n=5标记的行时,它不会再次为n=4编译同一行,而只是将Fibo<4>::x视为已声明。
template<> int Fibo<4>::x; // x defaults to 0

为什么会这样?为什么使用时能够按预期工作
template<int n> struct Fibo { enum { x = Fibo<n-1>::x + Fibo<n-2>::x }; };
template<> struct Fibo<0> { enum { x = 1 }; };
template<> struct Fibo<1> { enum { x = 1 }; };

你想使用非静态变量代替,但不使用静态变量吗?如何修复第一个代码(不使用enum)?

它在gcc-4.8.0中编译良好,输出为“8 5 3 2 1 1”。 - masoud
2
很好,我从来不知道你可以专门初始化静态成员。 - Kerrek SB
谢谢,M M。 我会添加一条注释,说明我使用了VC++。 - string QNA
静态变量初始化的顺序... - Jarod42
Clang输出0 0 1 2 1 1 - TemplateRex
3个回答

3
标准非常明确:
14.7.1 隐式实例化[temp.inst]
9 类模板的隐式实例化不会导致该类的任何静态数据成员被隐式实例化。
在main()中对Fibo::x的所有调用,其中n>1,都是显式实例化,通过斐波那契递归将隐式实例化Fibo和Fibo,但不包括它们的成员x。这意味着在这些点上,static成员x将被计算为默认初始化值0。对于n=1和n=0,编译器将看到1的显式初始化值。因此,您获得以下计算结果。
Fibo<5>::x --> Fibo<4>::x + Fibo<3>::x --> 0 + 0 = 0
Fibo<4>::x --> Fibo<3>::x + Fibo<2>::x --> 0 + 0 = 0
Fibo<3>::x --> Fibo<2>::x + Fibo<1>::x --> 0 + 1 = 1
Fibo<2>::x --> Fibo<1>::x + Fibo<0>::x --> 1 + 1 = 2
Fibo<1>::x --> 1
Fibo<0>::x --> 1

在计算斐波那契递归之前,您需要实例化静态成员x。您可以通过static const intenum成员x或函数(在C++11中可能是constexpr)来实现,如@Jarod42所示。


2

我不确定模板类 template<int n> int Fibo<n>静态变量 x = Fibo<n-1>::x + Fibo<n-2>::x; 的初始化顺序是否指定...

template <int N> struct Fibo { int operator()() const { static int x = Fibo<N - 1>()() + Fibo<N - 2>()(); return x; } };

template <> struct Fibo<1> { int operator()() const { static int x = 1; return x; } };
template <> struct Fibo<0> { int operator()() const { static int x = 1; return x; } };

依赖项得到了尊重。
[编辑]
如果值可能被修改(根据您的评论),您可以使用类似的技术但返回引用。
template <int N> struct Fibo {
private:
    int& operator()() { static int x = Fibo<N - 1>()() + Fibo<N - 2>()(); return x; }

public:
    int operator()() const { return const_cast<Fibo&>(*this)(); }
    // This change Fibo<0> and Fibo<1> and then update value up to Fibo<N>.
    int operator(int fibo0, int fibo1) {
        int n_1 = Fibo<N - 1>()(fibo1, fibo2);
        (*this)() = n_1 + Fibo<N - 2>()();
    }
};

template <> struct Fibo<1> {
private:
    int& operator()() { static int x = 1; return x; }
public:
    int operator()() const { return const_cast<Fibo&>(*this)(); }
    void operator(int fibo0, int fibo1) { Fibo<0>()(fibo0); (*this)() = fibo1; }
};

template <> struct Fibo<0> {
private:
    int& operator()() { static int x = 1; return x; }
public:
    int operator()() const { return const_cast<Fibo&>(*this)(); }
    void operator(int fibo0) { (*this)() = fibo0; }
};

我使用静态成员而不是枚举类型,因为有时我想在运行时更改它们的值。当然,更改斐波那契数列没有意义,但这只是一个简单的例子,我有一个更长的代码也遇到了同样的问题。 - string QNA
1
所以,你可以在我的例子中返回 int& 而不是 int,以便稍后修改该值... - Jarod42

0

我认为@Jarod42提出的解决方案过于复杂。

相比之下,考虑下面更简单的代码。

template<int N>
struct fib {
    static const int val = fib<N-1>::val + fib<N-2>::val;
};

template<>
struct fib<0> { static const int val = 0;};

template<>
struct fib<1> { static const int val = 1;};

int main() {
    std::cout << fib<45>::val << "\n";
    return 0;
}

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