模板内依赖参数的查找



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

然而,我发现了不同编译器之间的差异,我很想知道标准本身需要什么。

考虑以下代码:

#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)

这是一个ideone链接

因此,看起来好像编译器延迟了标识符查找直到模板实例化之后,此时它能够找到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 ?

就重载do_something而言,您需要对原始模板进行专门化:

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

编辑: As @MatthieuM。需要指出的是,如果您还需要重载函数,函数模板专门化可能会产生奇怪的结果(在某些时候,您可能需要这样做,因为函数模板不能部分专门化)。参见Matthieu链接到Herb Sutter的文章《为什么不特化函数模板?》查看完整的解释。

建议使用结构体包装的静态函数,这样可以实现部分专门化,并消除重载函数模板带来的名称解析问题。

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;
    }
};

最新更新