2013-04-18 154 views
3

我对模板上下文中的函数名查找感到困惑。我知道编译器在模板代码中延迟了依赖于参数的标识符查找,直到实例化模板。这意味着您有时可能会出现语法错误或在模板化代码中调用不存在的函数,除非您实际实例化模板,否则编译器不会投诉。模板中的参数相关查找

但是,我发现不同编译器之间存在差异,我很想知道标准本身需要什么。

考虑下面的代码:

#include <iostream> 

class Foo 
{ 
    public: 

    template <class T> 
    void bar(T v) 
    { 
     do_something(v); 
    } 
}; 

void do_something(std::string s) 
{ 
    std::cout << "do_something(std::string)" << std::endl; 
} 

void do_something(int x) 
{ 
    std::cout << "do_something(int)" << std::endl; 
} 

int main() 
{ 
    Foo f; 
    f.bar("abc"); 
    f.bar(123); 
} 

注意,模板成员函数Foo::bar调用非参数相关的全局函数称为do_something,这甚至还没有被宣布呢。

但是,GCC 4.6.3会高兴地编译上述程序。运行时,输出为:

do_something(std::string) 
do_something(int) 

Here's an ideone link

因此,看起来好像编译器延迟了标识符查找,直到模板实例化,此时它能够找到do_something

相比之下,GCC 4.7.2将不是编译上述程序。它产生以下错误:

test.cc: In instantiation of ‘void Foo::bar(T) [with T = const char*]’: 
test.cc:27:13: required from here 
test.cc:10:3: error: ‘do_something’ was not declared in this scope, and no declarations were found by argument-dependent lookup at the point of instantiation [-fpermissive] 
test.cc:19:6: note: ‘void do_something(int)’ declared here, later in the translation unit 

所以,GCC 4.7.2意识到do_something晚宣布,但拒绝编译程序,因为do_something不是争论有关。

因此,我假设GCC 4.7.2在这里可能是正确的,而GCC 4.6.3是不正确的。所以可能,我需要在Foo::bar定义之前声明do_something。这个问题是假设我想允许我的类Foo的用户通过执行它们自己的do_something过载来扩展Foo::bar的行为。我需要写类似:

#include <iostream> 

template <class T> 
void do_something(T v) 
{ 
    std::cout << "do_something(T)" << std::endl; 
} 

class Foo 
{ 
    public: 

    template <class T> 
    void bar(T v) 
    { 
     do_something(v); 
    } 
}; 

void do_something(int x) 
{ 
    std::cout << "do_something(int)" << std::endl; 
} 

int main() 
{ 
    Foo f; 
    f.bar("abc"); 
    f.bar(123); 
} 

这里的问题,是的do_something重载无法从Foo::bar中可见,因而不会被调用。所以即使我拨打do_something(int),它也会调用do_something(T)而不是int的超载。因此,对于GCC 4.6.3和GCC 4.7.2,上述程序输出:

do_something(T) 
do_something(T) 

那么这里有什么解决方案?我如何允许用户通过实施do_something自己的过载来扩展Foo::bar

+1

甚至引入了“一揽子”与最不理想的转换,问题仍然存在:http://ideone.com/OSGoqJ –

回答

2

至于超载do_something的话,你需要专注你的原始模板:

template<> 
void do_something<int>(int x) { 
    std::cout << "do_something(int)" << std::endl; 
} 

编辑:作为@MatthieuM。指出,函数模板专业化可能产生奇怪的结果,如果你还需要重载函数(在某些时候,你可能会需要,因为函数模板不能是部分专业)。有关完整说明,请参见Matthieu与Herb Sutter的文章Why Not Specialize Function Templates?的链接。什么是建议而不是

是使用静态函数包裹在一个结构,它允许部分专业化,并删除自带的重载函数模板名称解析问题。

template<typename T> 
struct DoSomething { 
    static void do_something(T v) { 
     std::cout << "do_something(T)" << std::endl; 
    } 
}; 

struct Foo 
{ 
    template <class T> 
    void bar(T v) { 
     DoSomething<T>::do_something(v); 
    } 
}; 

// Now you can specialize safely 
template<> 
struct DoSomething<int> { 
    static void do_something(int v) { 
     std::cout << "do_something(int)" << std::endl; 
    } 
}; 
+0

它将工作* *在这里,但是这是在一般一个可怕的想法。名称查找和模板专业化以非常奇怪的方式进行交互。 –

+0

@MatthieuM。谨慎阐述?据我了解模板专业化(这可能不是一个完整的理解,我承认)专业化是在实例化点选择。我意识到这可能是过度简化的,但我从来没有被这种咬伤(即使我经常使用它),所以我从来没有深入过。 ;) – syam

+0

,最好由Herb Sutters'阐释的,为什么并不是专门函数模板?](http://www.gotw.ca/publications/mill17.htm)。问题的关键是,首先“真正的”函数模板查到的,而最匹配,才把该编译器查找如果存在基函数模板的一个特例来考虑。所以,要知道你是否专业化选择与否,你需要知道哪些基函数模板,它涉及到...它的怪异,真的,真的,不可思议。 –