Name Resolution within a Template

Posted by zhangxiaojian on February 28, 2014

c++的template在遇到名称决议的时候,根据定义和实例化有两种不同的scope:

  1. scope of the template definition
  2. scope of the template instantiation

假设有一个模板定义在nameResolution.h文件中:

extern double foo(double);

template<class type>
class ScopeRules{
    public:
        void invariant()
        {
            member = foo(val);
        }
        type type_dependent()
        {
            return foo(member);
        }
    private:
        int val;
        type member;
};

模板中两个public函数都调用的名为foo的函数,但两者拥有不同的参数,一个参数是int型的成员变量,另一个参数的类型将等到模板被实例化的时候才能决定。在模板定义之前声明了一个定义在其它地方的foo函数,参数和返回值都为double。我们在一个名为foo.cpp的文件中定义foo函数,并且定义一个重载版本:

#include<iostream>

double foo(double var)
{
    std::cout<<"the foo of double is called"<<std::endl;

    return var;
}

int foo(int var)
{
    std::cout<<"the foo of int is called ....."<<std::endl;

    return var;
}

函数打印不同的输出以便区分。

假设我们在客户端这么调用:

extern int foo(int);

ScopeRules<int> sr0;
sr0.invariant();

那么哪一个foo函数会被调用?是在nameResolution.h文件中声明的double版还是在使用前声明的int版?如果按照正常的思维,由于模板参数实例化为int,成员变量var就为int类型,对两个foo函数进行重载,当然是选择int版的foo。但是模板有instantiation版的scope和definition版的scope,答案就不一定了。

Template中,对于一个nonmember name的决议结果,是根据这个name的使用是否与“用以实例化该template的参数类型”有关而定。如果使用没有关联,就以definition版scope决定,相反,就以instantiation版scope决定。上述invariant()中调用成员变量var,无论模板类型实例化为什么,var始终为int型,所以与template的实例化参数无关,definition scope中只有一个double类型的foo函数,调用别无选择,即使参数需要进行转换。

那么如果这样调用:

extern int foo(int);

ScopeRules<int> sr0;
sr0.type_dependent();

函数type_dependent()调用成员变量member,与template的参数类型有关,就要以definition scope 决定,definition scope中有两个重载的foo函数,根据重载规则,选择int版的foo函数。

皆大欢喜,是否就此结束? of crouse not!

将程序在vs2010中测试,所得结果出乎意料:

vs

两个函数的调用的都是int版的foo()。原来vs编译器采用的是wrong implementation,并没有区分模板的不同scope。而是以宏展开然后正常重载的方式,所以得到的结果都是调用int foo().

转移阵地,用g++编译运行,结果也出乎意料:

g++

这次调用的都是double版的foo(),显然g++做了区分,不过也不尽如人意。

so,使用的时候需谨慎,调用哪一个函数,编译器说了算。-.-