【问题标题】:C++ symbol scope search order different for template and non-template class?模板和非模板类的C++符号范围搜索顺序不同?
【发布时间】:2023-03-14 20:20:01
【问题描述】:
#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;
}

这给了expected result

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

我收到unexpected result:

global foo()

并且使用this-&gt; 不是一个选项,因为我正在尝试创建一个“后备”机制。

【问题讨论】:

  • call() { T::foo(); } 可以使用吗?它有效。
  • B::call 拨打T::foo(); 怎么样?
  • 与 11 Beta 相比,它运行良好。调用 A::foo()。不确定是否可以考虑 beta 编译器。
  • 在 VS11 Beta 中,调用 ::foo() 调用全局函数,而 foo() 调用 A::foo()。
  • @elmo:这可能对阅读gcc.gnu.org/onlinedocs/gcc/Name-lookup.html很有趣

标签: c++ templates scope


【解决方案1】:

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

模板内的名称分为两种:

Dependent – 依赖于模板参数但不在模板中声明的名称。

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

当编译器试图解析代码中的某个名称时,它首先判断该名称是否依赖,而解析过程源于这种区别。虽然“正常”解析非依赖名称 - 当定义模板时,依赖名称的解析发生在模板实例化的点。

您的示例中的B::call 中的foo(); 是一个非依赖名称,因此在模板定义时它被解析为全局foo()

【讨论】:

  • 这与 Jagannath 的评论“它与 11 Beta 工作得很好。调用 A::foo()。”有什么关系?我知道 MS 不是验证任何标准的最佳选择。此外,任何对 C++ (03) 标准的引用都会很好。你知道如何让它按照我的意愿行事吗?
  • @elmo:Microsoft C++ 没有正确实现两阶段名称查找。见this question
  • @elmo: 让它按你的方式工作 : template struct B : public T { void call() { T::foo();//将 T:: 添加到代码中} };
  • @elmo:标准的相关部分是C++11 14.6.2/3:“在类或类模板的定义中,如果一个基类依赖于一个模板-parameter,在类模板或成员的定义点或类模板或成员的实例化期间,在非限定名称查找期间不检查基类范围。” (如果您出于某种原因对历史标准感兴趣,C++03 14.6.2/3 或多或少说了同样的话)。
【解决方案2】:

接受的答案解释了您看到该行为的原因,但没有解释如何实现您想要的“后备”行为。这可以使用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()
}

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

【讨论】:

    【解决方案3】:

    你需要明确告诉使用T类方法。

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


    但至于回退机制,你可以检查这个问题:Is it possible to write a template to check for a function's existence?

    使用Substitution failure is not an error (SFINAE),您可以检查 T 中的方法 foo,然后运行正确的方法。

    【讨论】:

    • 请看他的评论和他问题的最后一行。
    • 再次:我问题的最后一行清楚地表明它不起作用:ideone.com/YuoIH
    • @elmo:除了C 结构没有foo 方法时,你为什么还要工作? ideone.com/WcLQE
    • @Jagannath:我的意思是“编译器应该在字符串 33 (c.call()) 中链接什么方法/函数”? struct C没有fooB没有foo,这种情况应该叫什么?
    • 是的,我的错。没看到。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-16
    • 1970-01-01
    • 1970-01-01
    • 2020-10-20
    • 2014-09-30
    • 1970-01-01
    相关资源
    最近更新 更多