C++模板实例化的时机

4

我想知道为什么以下两段代码表现非常不同。我可以理解第二个不起作用的原因,但为什么第一个可以工作呢?在相同的地方int x = gc.f();模板应该被实例化,所以会发生相同的错误,但为什么实际上没有错误呢?

a.cpp

#include <iostream>

using namespace std;

template <typename T>
struct A {
    struct B {
    };
};

template <typename T>
struct C {
    typedef A<C<T> > D;

    int f() {
        typename D::B p;
        return 0;
    }
};

C<float> gc;
int x = gc.f();

template <typename T>
struct A<C<T> > {
    struct B {
        B() {
            cout << "B::B()" << endl;
        }

        ~B() {
            cout << "B::~B()" << endl;
        }
    };
};

int main() {
}

输出

B::B()
B::~B()

and

a2.cpp

#include <iostream>

using namespace std;

template <typename T>
struct A {
    struct B {
    };
};

struct C {
    typedef A<C> D;

    int f() {
        D::B p;
        return 0;
    }
};

C gc;
int x = gc.f();

template <>
struct A<C> {
    struct B {
        B() {
            cout << "B::B()" << endl;
        }

        ~B() {
            cout << "B::~B()" << endl;
        }
    };
};

int main() {
}

编译器错误

a2.cpp:24: error: specialization of ‘A<C>’ after instantiation
a2.cpp:24: error: redefinition of ‘struct A<C>’
a2.cpp:6: error: previous definition of ‘struct A<C>’

时间与此有什么关系? - Lightness Races in Orbit
抱歉,实际上我想问的是在实例化后‘A<C>’的专业化问题。 - Kan Li
3个回答

3
您实际上要求两件不同的事情,尽管它们都与模板实例化相关。
第一段代码为什么编译?
标准规定,模板的实际实例化在整个翻译单位被处理之后执行,这意味着实际的模板实例化将在该翻译单位中定义的所有类型都完成之后进行,即使实例化点不同并且在翻译单元中更早。更多信息请参见此问题
第二个例子为什么不能编译?
第二个例子的问题在于标准要求模板的特化必须在第一次使用该特化之前进行声明。
注意这里有两个不同的概念。“实例化点”指的是在代码中实例化发生的位置,而不是实例化的时间。在您的示例中,“实例化点”是表达式“C< float > gc;”,而“时间”在所有其他情况下都是“在整个翻译单位被处理之后”。

+1 我认为你基本上是正确的,但这里有一些微妙之处可能需要更多的解释。例如,在第一个示例中,如果 C 有一个类型为 D::B 的成员,并且 A<C<float>> 有一个完全特化,则仍将出现实例化错误。 - Vaughn Cato
@VaughnCato,如果在实例化点之前或之后有一个完整的A<C<float>>特化吗?如果是在之后,那么我们回到了第二种情况。如果是在实例化点之前,我认为(不是100%确定,需要测试)它不应该无法编译。 - David Rodríguez - dribeas
我在谈论将A<C<float>>完全特化而不是在与case 1相同的位置进行部分特化。似乎对已经实例化的类进行完全特化会导致错误,而部分特化则不会(尚未在标准中找到),这就是为什么case 1有效的另一个原因。现在,如果您只将部分特化切换为完全特化,则case 1仍将有效,显然是由于您所说的原因。但是我看到的是实例化位于成员函数体内。 - Vaughn Cato
@VaughnCato:你要找的引用是14.7.3p6(在答案中)。显式特化是你所指的完全特化。 (14.7.3p1 显式特化[...]可以由以template<>引入的声明声明) 14.7.3p6规定它必须在第一次使用特化之前(上面)声明。 - David Rodríguez - dribeas
谢谢,我没意识到“explicit”是正确的术语。似乎这个术语很奇怪,因为“explicit”更像是“implicit”的相反而不是“partial”的相反。无论如何,显式专门化在某些方面与部分专门化处理方式不同,这就是案例1能够工作的原因之一。 - Vaughn Cato
@VaughnCato:我认为这只是我们对于特化在标准中的精确含义与一般含义之间理解上的问题。我的看法是,特化是将模板应用于一组固定的具体参数。在这种情况下,完全特化明确地固定了所有这些参数,而部分特化则为编译器提供了隐式地为一些参数子集进行特化的方式。 - David Rodríguez - dribeas

2

您只需要删除不必要的typename限定符并稍微调整一下顺序。正如第二个错误信息所说,您已经在C中实例化了A<C>,但之后又对其进行了特化。要解决此问题,您可以将A<C>的特化移动到C的定义之前,并使用前向声明来消除未声明的标识符错误。

以下是修改后的代码:

#include <iostream>

using namespace std;

template <typename T>
struct A {
    struct B {
    };
};

struct C; // Forward declaration of C

template <>
struct A<C> {
    struct B {
        B() {
            cout << "B::B()" << endl;
        }

        ~B() {
            cout << "B::~B()" << endl;
        }
    };
};

struct C {
    typedef A<C> D;

    int f() {
        D::B p;
        return 0;
    }
};

C gc;
int x = gc.f();


int main() {
}

1
因为typename仅在模板内部指示类型时使用。

当不需要时,禁止实际上似乎很奇怪。 - Lightness Races in Orbit
使用gcc C++0x编译不会出现typename错误(链接)。然而,旧版本会出现错误(链接)。因此,我认为规则在c++03和c++11之间发生了变化。 - Jesse Good
3
我认为最好不允许在不必要的地方使用 typename。否则,人们可能会在遇到问题时随意地使用它,而不是弄清楚哪些地方实际上需要它。 - bames53
找到相关引用:与typename前缀一样,只要不是绝对必要的情况下,模板前缀也是允许的;也就是说,当嵌套名说明符或->或.左侧的表达式不依赖于模板参数,或者该使用未出现在模板作用域中时。 14.2.5 - Jesse Good
抱歉,实际上我想问的是关于实例化后'A<C>'的特化问题。 - Kan Li

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