【问题标题】:Scope problems in template C++模板 C++ 中的范围问题
【发布时间】:2010-09-30 08:40:44
【问题描述】:

这个程序有没有作用域问题?

#include<iostream>

using namespace std;

template<class Type>
class Base
{
   public:
   Type member;
   Base(Type param): member(param){

   }
};

template<class Type>
class Derived: public Base<Type>
{
    public:               
    Derived(Type param):Base<Type>(param){
     //                       ^
     //                       |_______  Not writing Type here gives error, why?
    }
    void display()
    {
        cout << member; /** ERROR HERE **/
    }
};

int main()
{
   Derived<int> p(5);
   p.display();
   return 0;
}

我收到错误 'member' was not declared in this scope。如何解决问题?

【问题讨论】:

    标签: c++ templates


    【解决方案1】:

    您的问题有点令人困惑。起初我以为你在询问成员初始化列表中的base&lt;Type&gt;,然后我以为你在询问访问member,然后回到第一个......现在我想你在问两个,所以我都会回答。


    这里不写Type会报错,为什么?

    当您使用类模板的名称 (my_class_templ) 时,它指的是 模板,它不是类型。为了将其用作类型,您需要提供模板参数(my_class_templ&lt;int&gt;my_class_templ&lt;T&gt;)。因此,无论何时需要类型名称(包括初始化列表中的基类名称),您都需要提供模板参数。

    您可以省略类模板定义中类模板名称的模板参数列表。例如,复制构造函数可以声明为

     my_class_templ(const my_class_templ& rhs);
    

    而不是

     my_class_templ<T>(const my_class_templ<T>& rhs);
    

    这只是一个小语法糖,让你打字更少。

    但是,在类模板定义之外,您需要明确说明所有模板参数。对于派生类也是如此:

    my_dervied_class_templ(const my_derived_class_templ& rhs)
     : my_class_templ<T>(rhs)                          // need to spell out <T> here
    {
    }
    

    我收到错误 'member' was not declared in this scope。如何解决问题?

    当编译器首先遇到您的模板时,只有它的定义,编译器还没有看到任何实例化。编译器不知道在实例化点是否可能存在模板的特化范围。但是,您可以将模板专门用于 Base&lt;T&gt;::member 以引用其他内容或不完全定义。 (比如说,特化Base&lt;void&gt; 没有数据成员。)因此,编译器不能推测Base 的成员。因此,它们将不会在Derived 中被查找。

    这样做的结果是,如果你需要引用Base 的成员之一,你需要告诉编译器你期望Base&lt;T&gt; 有这样一个成员。这是通过完全限定其名称来完成的:Base&lt;Type&gt;::member

    【讨论】:

    • 实际上初始化列表中的基类说明符问题和member问题都具有相同的起源。您可以省略模板参数列表并找到Base&lt;Type&gt; 的注入类名称(这是一个非常好的类型名称)。然而问题是 Base 是非依赖的,因此无法在依赖的基类中查找。如果他说:Derived::Base(param),这将非常有效(仅在从 v4.5 开始的 GCC 上,因为他们之前没有正确实现注入类名查找)
    • @Johannes:嗯,明白了这一点。 :Derived::Base(param) 适用于 g++ (4.5) 和 CLang
    • @Johannes:顺便说一句,在 MSVC++2010 上一切正常:D :Derived::Base(param):Base(param):Base&lt;Type&gt;(param):Derived&lt;Type&gt;::Base&lt;Type&gt;(param) 都是等效的。此外,即使只写member 也可以。不需要它的完全限定名称。
    • @Prasoon:那是因为 VC still 没有进行正确的两阶段查找。
    【解决方案2】:

    这里不写Type会报错,为什么?

    如果省略Type,编译器将无法确定Base 是基类还是Derived 的成员。指定Type 确保Base 是一个模板类[基类]。

    “成员”未在此范围内声明

    这与名称查找规则(依赖基类)有关。

    C++03 [第 14.6/8 节] 说

    在查找模板定义中使用的名称声明时,通常的查找规则(3.4.1、3.4.2)用于非依赖名称。依赖于模板参数的名称查找推迟到知道实际模板参数之后 (14.6.2)。

    现在Section 14.6.2/3

    在类模板或类模板成员的定义中,如果类模板的基类依赖于模板参数,则在非限定名称查找期间不会检查基类范围类模板或成员的定义点或类模板或成员的实例化期间。

    member 是一个非限定名称,因此不会检查基类。

    所以你有两个选择。

    1. 使用Member 的完全限定名称,即Base&lt;Type&gt;::member
    2. 使用this-&gt;member

    【讨论】:

    • 请注意我对@sbi 回答的评论 :)
    【解决方案3】:

    在编译器读取模板时(而不是在实例化模板时),它无法分辨 Base&lt;Type&gt; 是什么(它可能是专门的),因此不会尝试推断它具有 member 成员.你必须明确告诉它:cout &lt;&lt; this-&gt;Base&lt;Type&gt;::member;

    我认为(检查一下,我不确定)using Base&lt;Type&gt;::member 在类范围内也可以工作。

    【讨论】:

    • this->member 不也可以吗?如果我没记错的话,这在所有编译器中的实现方式并不相同:gcc chokes on this example but msvc 可能不会。
    • @stijn:MSVC 在名称查找方面特别松懈......我被这个咬了无数次。
    • MSVC 没有实现两阶段查找,这就是它在模板名称查找上错误地宽松的原因。根据经验,如果 gcc 和 MSVC 在一段代码是否合法的问题上存在分歧,那么 gcc 通常是正确的(MSVC 在最近的版本中变得更好,即 VS2008 和 2010)。
    【解决方案4】:

    C++ 标准要求编译器对模板进行“两阶段查找”。也就是说,他们试图在解析模板时的第一阶段解析所有非依赖名称(不依赖模板参数的名称),并在实例化模板时在第二阶段解析所有依赖名称。

    如果您不符合member 的条件,它将被视为非依赖名称,并且在第一阶段查找失败。您可以通过在前面加上 this-&gt; 来解决这个问题。这使得 member 成为依赖名称,并且查找会延迟,直到您实际实例化模板。

    【讨论】:

      【解决方案5】:
      Derived(Type param):Base<Type>(param){ 
      

      Base&lt;Type&gt; 是必需的,因为 Derived 的基数是 Base&lt;T&gt;。没有什么叫Base

      void display()   
      {   
              //cout << member; /** ERROR HERE **/ 
              cout << this->member;
              cout << this>Base<Type>::member;  
      }
      

      在“派生”范围内使用 using 声明也是一种有效的技术。

      【讨论】:

      • 请注意我对@sbi 回答的评论 :)
      • @Johannes Schaub - litb:对不起,但我认为我没有完全理解它。 'Base' 的注入名称是 'Base' 还是只是 'Base'?
      • 它是BaseBase&lt;Type&gt; 是注入的类名,后跟一个参数列表。见14.6.1/1。试试struct A : std::vector&lt;int&gt; { A():vector() { } };。关于完美运行的符合要求的实现。请注意,在类头或构造函数初始化列表中用作基类的依赖限定类型名称之前不需要typename
      猜你喜欢
      • 2010-11-12
      • 2015-11-12
      • 1970-01-01
      • 2011-01-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-11
      • 1970-01-01
      相关资源
      最近更新 更多