0

AFAIK 模板实例化只有两个“阶段”:

  • 默认(立即),
  • 延迟(懒惰)。

默认用于以下成员:

  • typedef年代,
  • 成员字段。

延期:

  • 功能(静态或非静态),
  • 嵌套类型。

这是正确的,还是有其他一些关于编译器渴望实例化模板的陷阱/规则?

4

1 回答 1

5

完全不正确。模板实例化只有一个阶段。何时(何地,这是一个重要的点)取决于它的使用方式。我认为您将名称查找与实例化混淆了。

名称查找分两个阶段完成。(至少如果编译器是一致的——VC++ 仍然会出错。)第一个发生在编译器解析模板定义时;第二个当模板被实例化时。符号是在第一阶段还是第二阶段查找取决于它是否依赖。确定这一点的规则相当复杂,但总的来说:

  • 如果它是一个函数名,并且它的一个或多个参数依赖于模板参数,则函数名是依赖的,它将被查找(和函数重载决议)将发生在实例化时,而不是之前。

  • 如果名称由模板参数限定(例如 T::somethingT模板参数在哪里),则它是依赖的。

  • 在类模板的成员中,如果有一个依赖基类(一个在某种程度上依赖于模板参数的基类),那么右边的任何东西this->都是依赖的。

(实际的规则要复杂得多,但上面是一个粗略的近似值,对于大多数用途来说可能已经足够了。)

编辑:

只是一些差异的例子:

class MyType {};

void func0( double d )
{
    std::cout << "called func0( double )" << std::endl;
}

void func1( double, MyType const& )
{
    std::cout << "called func1( double )" << std::endl;
}

template <typename T>
int funcT( T const& param )
{
    func0( 42 );            //  non-dependent
    func1( 42, param );     //  dependent
}

void func0( int d )
{
    std::cout << "called func0( int )" << std::endl;
}

void func1( int, MyType const& )
{
    std::cout << "called func1( int )" << std::endl;
}

int
main()
{
    funcT( MyType() );
    return 0;
}

输出应该是

called func0( double )
called func1( int )

func0对in的调用funcT是不依赖的,因此名称查找仅在定义模板时发生,而 不是在稍后实例化时发生。那时,只有一个func0,所以它被调用。

func1对in的调用funcT是依赖的(因为它的第二个参数取决于模板参数),因此在实例化点会有一个额外的名称查找(main在这种情况下紧跟在 之后)。此附加查找仅使用 ADN,但由于其中一个参数是在全局命名空间中定义的,因此 ADN 将在全局命名空间中查找,并找到第二个声明func1as(然后将由重载决议选择,因为它是更好的匹配)。

请注意,我无法验证这一点,因为目前我只能访问已损坏的 VC++11。它很微妙,所以我很可能错过了一些东西。一般规则是避免这种歧义。

请注意,如果func0没有在模板定义之前声明,则代码不应编译。根据经验,从预标准编译器(或 VC++)迁移时,这是最常见的错误原因。

另一个有时令人惊讶的常见情况:

template <typename Base>
class Derived : public Base
{
public:
    Derived()
    {
        init();         //  Non-dependent lookup, will NOT find any
                        //  function init() in Base!!!
        this->init()    //  Dependent lookup, WILL find
                        //  Base::init, if it exists.
        Base::init()    //  Also dependent.
    }
};

通常,如果您使用这种模式,并在像 VC++ 这样的预标准编译器上进行开发,那么当您转向更现代的编译器时,您会遇到许多编译器错误。它们很容易通过添加this->. 但是,如果在定义模板时 还有一个init可见的全局函数,您将调用它,而不是基类中的函数。如果您为您的函数选择有意义的名称,并将全局函数放在命名空间中,您可能不会经常受到这种无声的语义变化的影响,但请注意。

于 2013-03-26T08:45:54.197 回答