【问题标题】:Why does the C++ standard specify that unqualified names in a template are non-dependent?为什么 C++ 标准规定模板中的非限定名称是非依赖的?
【发布时间】:2021-12-09 01:53:12
【问题描述】:

为什么 C++ 标准规定模板中的非限定名称是非依赖的?

例如

template<typename T>
class Base
{
public:
    T x;
};

template<typename T>
class C : public Base<T>
{
public:
    bool m() { return x == 0; } // Error: undeclared identifier 'x'
};

从接受的答案中引用 SO question 关于如何克服限制:

标准规定模板中的非限定名称是 非依赖的,必须在定义模板时查找。这 依赖基类的定义当时是未知的 (可能存在基类模板的特化)所以不合格 名称无法解析。

但是,引用的答案和其他答案没有说明为什么这是标准规定的。这种限制的理由是什么?

【问题讨论】:

    标签: c++ templates


    【解决方案1】:

    这并不是标准实际所说的。它实际上说的是在非限定名称查找期间不检查依赖基类。给定正确的上下文,非限定名称当然可以依赖 - 这就是模板中 ADL 的工作方式:给定模板类型参数 Tfoo(T()) 中的 foo 是依赖名称。

    无论如何,它不能进行这种查找的原因很简单:在模板定义时,你不知道依赖的基类会是什么样子,这要归功于以后可能会出现的特化,所以你不能做任何有意义的查找。具有依赖基类的模板中的每个拼写错误的标识符在实例化之前都无法诊断,如果有的话。而且由于每个非限定标识符都可能来自依赖基类,因此您需要某种方式来回答“这是一种类型吗?”和“这是一个模板吗?”,因为这些问题的答案会影响解析。这意味着类似于可怕的 typenametemplate 关键字,但在更多的地方。

    【讨论】:

    • 但是只要在类范围内添加using T::x,限制就会消失,即使可能存在使x无效的特化(在这种情况下,我猜实例化会出错)。所以我想以类似的方式,在编译器对模板的第二次传递中,它可以自己使用T::x,或者找出不合格的名称x是无效的。
    • @Danra 是的,它可以在实例化时弄清楚。关键是编译器将无法在模板定义时进行任何拼写检查,因为每个拼写错误都是可能的依赖基成员。
    • 这是一个很好的观点。也许这就是它背后的理由。不过,从理论上讲,我猜如果编译器和它们运行的​​机器足够快,以至于代码编辑器可以完全编译所有代码,包括即时模板实例化,那么拼写错误就可以推迟到那个阶段。
    • @Danra 模板代码的作者可能不是实例化模板的代码代码的作者。 (想想只有标题的模板库。)
    【解决方案2】:

    我认为这是一个一致性问题。考虑一下这个,稍微修改一下的例子:

    头文件:

    template<typename T>
    class Base
    {
    public:
        T x;
    };
    
    extern int x;
    
    template<typename T>
    class C : public Base<T>
    {
    public:
        bool m() { return x == 0; }
    };
    

    和源文件:

    template<>
    class Base<int> // but could be anything
    {
    public:
        // no x here
    };
    
    int main()
    {
      C<char> c1;
      c1.m(); // may select ::x or Base<char>::x
      C<int> c2;
      c2.m(); // has only one choice: ::x
      return(0);
    }
    

    标准中的措辞保证编译器要么出错,要么选择它在模板定义点看到的任何符号。如果编译器将名称解析推迟到模板实例化,它可能会选择此时可见的不同对象,这可能会让开发人员感到惊讶。

    如果开发人员想要访问依赖名称,他必须明确说明这一点,他不应该因此措手不及。

    另请注意,如果 x 在模板定义时不可用,则以下代码将破坏(意外)一个定义规则:

    一个.cpp

    #include <template_header>
    
    namespace {
    int x;
    }
    
    void f1()
    {
      C<int> c1;
    }
    

    两个.cpp

    #include <template_header>
    
    namespace {
    char x;
    }
    
    void f2()
    {
      C<int> c2;
    }
    

    人们会期望 c1 和 c2 变量具有相同的类型,例如可以安全地传递给采用 C&lt;int&gt; const &amp; 参数的函数,但基本上这两个变量具有相同的类型名称但实现不同。

    【讨论】:

    • 我们总是可以在这个替代宇宙中制定一条规则,即我们在依赖基础的主要模板中找到的任何东西都是 x 在实例化时绑定的。
    • 我相信这会破坏完整和部分模板专业化。
    【解决方案3】:

    无论是否符合标准,在 Visual Studio 2015 中 - 这根本不是问题。此外,尽管来自 Tomek 的评论,编译器还是采用了正确的 x

    static int x = 42;
    
    template<typename T>
    class Base {
    public:
        T x;
        Base() { x = 43; }
    };
    
    template<>
    class Base<int> { };
    
    template<typename T>
    class C :public Base<T>
    {
    public:
        int m() { return x; }
    };
    
    void main() {
        C<int> cint;
        C<char> cchar;
    
        std::cout << "cint: " << cint.m() << std::endl;
        std::cout << "cchar: " << cchar.m() << std::endl;
    }
    
    // Output is:
    // cint: 42
    // cchar: 43
    

    【讨论】:

    猜你喜欢
    • 2013-06-28
    • 1970-01-01
    • 1970-01-01
    • 2017-12-02
    • 1970-01-01
    • 1970-01-01
    • 2021-09-14
    • 2018-08-07
    相关资源
    最近更新 更多