模板类C++符号范围搜索顺序不同

C++ symbol scope search order different for template and non-template class?

本文关键字:搜索 顺序 范围 符号 C++      更新时间:2023-10-16
#include <iostream>
void foo()
{
    std::cout << "global foo()" << std::endl;
}
struct A {
    void foo()
    {
        std::cout << "A::foo()" << std::endl;
    }
};
struct B : public A {
    void call()
    {
        foo();
    }
};
int main(int argc, char **argv )
{
    B b;
    b.call();
    return 0;
}

这给出了预期的结果:

A::foo()

但是,在更改两行(B 类到模板)后:

#include <iostream>
void foo()
{
    std::cout << "global foo()" << std::endl;
}
struct A {
    void foo()
    {
        std::cout << "A::foo()" << std::endl;
    }
};
template <typename T> // change here
struct B : public T {
    void call()
    {
        foo();
    }
};
int main(int argc, char **argv )
{
    B<A> b; // and here
    b.call();
    return 0;
}

我得到意想不到的结果:

global foo()

使用this->不是一种选择,因为我正在尝试创建"后备"机制。

你得到的是预期的结果。这在C++标准中称为"两阶段名称查找"。

模板内的名称分为两种类型:

从属 – 依赖于模板参数但未在模板中声明的名称。

非依赖 – 不依赖于模板参数的名称,以及模板本身的名称和在其中声明的名称。

当编译器尝试解析代码中的某个名称时,它首先确定该名称是否相关,解析过程源于这种区别。虽然非依赖名称是"正常"解析的 – 定义模板时,从属名称的解析发生在模板实例化点。

示例中B::call foo();是非依赖名称,因此在模板定义时将其解析为全局foo()

公认的答案解释了为什么您会看到这种行为,但没有解释如何实现您想要的"后备"行为。这可以使用 SFINAE 来完成,方法是引入一对成员模板重载,其中之一仅在基类具有名为 foo 的成员函数时才存在。

template <typename T>
struct B : T {
    template <void (T::*)()> struct has_mem_fn {};
    template <typename U> void call(has_mem_fn<&U::foo>*) {this->foo();}
    template <typename U> void call(...) {foo();}
    void call() {call<T>(0);}
};
struct X {};
int main()
{
    B<A> ba;
    ba.call();  // A::foo()
    B<X> bx;
    bx.call();  // global foo()
}

更新:我刚刚注意到您在另一个答案中的评论,您说您知道此方法,但由于必须支持功能失调的编译器而无法使用它。那样的话,恐怕你想要的恐怕是不可能的。

你需要特别告诉使用 T 类方法。

template <typename T>
struct B : public T {
    void call()
    {
        T::foo();
    }
};


但至于回退机制,你可以检查这个问题:是否可以编写一个模板来检查函数是否存在?

使用替换失败不是错误 (SFINAE),您可以检查 T 中foo的方法,然后运行正确的方法。