代码之家  ›  专栏  ›  技术社区  ›  Elazar Leibovich

C++模板隐藏父成员

  •  5
  • Elazar Leibovich  · 技术社区  · 16 年前

    通常,什么时候 A 正在从继承 B ,的所有成员 对自动可见 例如,的函数

    class A {
    protected:
        int a;
    };
    
    class B : public A {
        int getA() {return a;}
        //no need to use A::a, it is automatically visible
    };
    

    但是,当我使用模板继承时,此代码将变得非法(至少在 gcc )

    template<typename T>
    class A {
    protected:
        int a;
    };
    
    template<typename T>
    class B : public A<T> {
        int getA() {return a;}
    };
    
    templt.cpp: In member function `int B<T>::getA()':
    templt.cpp:9: error: `a' undeclared (first use this function)
    templt.cpp:9: error: (Each undeclared identifier is reported only once for each
    function it appears in.)
    

    我必须做一个

    class B : public A<T> {
        using B::a;
        int getA() {return a;}
    };
    
    class B : public A<T> {
        using A<T>::a;
        int getA() {return a;}
    };
    
    class B : public A<T> {
        int getA() {return B::a;}
    };
    

    等等,好像变量 a 被另一个变量隐藏 ,在以下情况下:

    class HiddenByOverload {void hidden(){}}
    class HidesByOverload : public HiddenByOverload {
        void hidden(int i) {} //different signature, now `hidden` is hidden
        void usehidden() {
            HiddenByOverload::hidden(); // I must expose it explicitly
        }
    }
    

    为什么会这样?还有其他方法阻止C++隐藏父模板类的变量吗?

    编辑: 感谢大家的精彩讨论。我必须承认我没有遵循引用C++标准段落的论点。我很难在没有阅读实际来源的情况下去理解它。

    我能做的最好的一件事就是引用“蟒蛇禅”中的一句短句:

    如果实施困难 解释一下,这(可能)是个坏主意。

    3 回复  |  直到 16 年前
        1
  •  6
  •   avakar    16 年前

    你也可以这样做

    class B : public A<T> {
        int getA() {return this->a;}
    };
    

    问题是成员位于基中,这取决于模板参数。正常的非限定查找是在定义点执行的,而不是在实例化点执行的,因此它不搜索依赖基。

        2
  •  6
  •   Johannes Schaub - litb    16 年前

    由于存在以下问题:不合格名称的依赖性如何,或者不合格名称查找对依赖名称的适用性如何:


    跟踪分析:确定如何分析语句

    如果在模板中遇到依赖名称,则始终假定 要命名类型, 除非 适用的名称查找会发现它是一个类型,或者我们在名称前面加上 typename :

    template<typename T>
    void f() {
      T f0; // T is a template type parameter => type
      T *f1;
    
      typename T::name g1; // "name" is assumed to be a type. 
      T::name g0; // "name" cannot be looked up here => non-type
    }
    

    此名称查找用于确定它是否为类型的操作始终在所有相关名称的模板定义点进行:它将以下分析引导到特定方向。在第二个语句中,我们将分析 T *f1 作为指针的声明,但是 作为乘法。在最后一句话中,我们 假定的 在分析前消除歧义时, T::name 不是类型,请尝试将其作为表达式进行分析。这将失败,因为我们希望在 T::姓名 . 此查找无论名称是否为类型,都不会影响名称在后面阶段的含义:它还不会将名称绑定到任何声明。


    实际解析

    在确定了什么是类型和什么不是类型之后,我们将实际分析模板。不依赖的名称——也就是说,那些没有在依赖的范围中查找的名称,或者那些没有被其他规则显式依赖的名称——在模板中使用时被查找,并且它们的含义不受实例化时可见的任何声明的影响。

    那名字 依赖项在实例化时被查找,既在模板定义中使用它们,也在模板的实例化位置。这同样适用于依赖于以下条件的非限定名称:

    template<typename T>
    struct Bar {
      void bar() { foo(T()); }
    };
    
    namespace A {
      struct Baz { };
      void foo(Baz); // found!
    }
    
    int main() { Bar<A::Baz> b; b.bar(); }
    

    这个 不合格的 foo 因为参数 T() 依赖于类型。在实例化时,我们将查找调用的函数 在模板定义周围使用非限定名称查找,并使用与参数相关的查找(粗略地说,在 T )围绕模板定义和实例化它的点(在 main )依赖于参数的查找将找到 .

    如果 Bar 现在有一个依赖的基类,非限定查找必须忽略该依赖的基类:

    template<typename T>
    struct HasFoo { };
    
    template<typename T>
    struct Bar : HasFoo<T> {
      void bar() { foo(T()); }
    };
    
    namespace A {
      struct Baz { };
      void foo(Baz); // found!
    }
    
    template<>
    struct HasFoo<A::Baz> {
      void foo();
    };
    
    int main() { Bar<A::Baz> b; b.bar(); }
    

    这肯定还没找到 A::foo ,尽管不合格的名称查找会在完成后找到类成员函数(如果找到类成员函数,ADL将找不到其他函数)。但是非限定名称查找找不到该函数,因为它是从属基类的成员,并且在非限定名称查找期间忽略这些函数。另一个有趣的案例:

    template<typename>
    struct A {
      typedef int foo;
      operator int() {
        return 0;
      }
    };
    
    // makes sure applicable name-lookup
    // classifies "foo" as a type (so parsing will work).
    struct TypeNameSugar {
      typedef int foo;
    };
    
    template<typename T>
    struct C : A<T>, TypeNameSugar {
      void c() {
        A<T> *p = this;
        int i = p->operator foo();
      }
    };
    
    int main() {
      C<void>().c();
    }
    

    标准规定,在查找 operator foo 名称,我们将在 p-> (属于班级范围) A<T> 以及完整表达式出现的范围(即 C<T>::c 作为非限定名),并比较两个名称(如果找到)是否指定相同的类型。如果我们愿意 忽略依赖的基类 A & T;T & GT; 在完整表达式范围内的查找过程中,我们将发现 FOO公司 在两个基类中,因此具有歧义。忽略 A & T;T & GT; 这意味着当我们查找到 P & GT; 以及另一次我们查找 TypeNameSugar .

        3
  •  2
  •   Matthieu M.    16 年前

    这是一个常见的问题,但是完全有可能绕过它,无论是函数、类型还是属性。

    问题出在模板类和函数的两个阶段评估的实现上。通常,标准要求对模板进行两次评估:

    • 第一次遇到时,验证它的格式是否正确
    • 第二个在声明时,为给定的类型实际生成代码

    在第一次评估中,模板参数是未知的,因此不可能知道基类将是什么…尤其是如果它包含 a 成员。任何不依赖于模板参数的符号都应明确定义并进行检查。

    通过显式定义范围,您将通过使符号依赖于模板参数,将检查延迟到第二个计算。

    使用 Boost 作为灵感:

    template <class A1, class A2, class A3>
    class MyClass: public Base<A1,A2,A3>
    {
    public:
      typedef Base<A1,A2,A3> base_;
    
      void foo()
      {
        // Accessing type
        bar_type x;             // ERROR: Not dependent on template parameter
        typename base_::bar_type x;
    
        // Accessing method
        bar();                  // ERROR: Not dependent on template parameter
        this->bar();
        base_::bar();
    
        // Accessing attribute
        mBar;                   // ERROR: Not dependent on template parameter
        this->mBar;
        base_::mBar;
      };
    
    }; // class MyClass
    

    我喜欢 促进 定义一个 base_ 内括号。首先,它当然有助于定义构造函数,其次,通过显式地限定来自基类的内容,它使那些遍历代码的人能够清楚地了解这些内容。