【问题标题】:C++ templates hides parent membersC++ 模板隐藏父成员
【发布时间】:2010-03-07 11:09:20
【问题描述】:

通常,当A 继承自B 时,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;}
};

等等。就好像变量aB的另一个变量隐藏了一样,如下情况:

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++ 标准段落的论点。如果不阅读实际来源,我很难理解它。

我能做的最好的总结讨论是引用“Python 之禅”中的一句话:

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

【问题讨论】:

标签: c++ generics templates


【解决方案1】:

你也可以这样做

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

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

【讨论】:

  • 我现在明白为什么会这样了。但我不明白为什么标准是以这种方式设计的。如果我在模板中,为什么我不想在模板实例化中查找内容。我唯一能想到的是,它使编译器的实现更容易。
  • 次要名称:14.6.2 从属名称:第 3 段:在类模板或类模板成员的定义中,如果类模板的基类依赖于模板参数,在类模板或成员的定义点或在类模板或成员的实例化期间,在非限定名称查找期间检查基类范围。跨度>
  • 该标准似乎预见到模板会被解析两次:在第一次传递期间,编译器会检查任何非依赖项,在实例化时它还会检查依赖项。 (但 VC++ 似乎在很大程度上忽略了第一遍,并接受了你的代码。)
  • @dirkgently,很有趣。我徘徊为什么该子句存在,因为在实例化点没有执行不合格的查找(据我所知)。不合格的名称在定义时被绑定。
  • 查看我刚刚发送给clang的问题报告:llvm.org/bugs/show_bug.cgi?id=6532
【解决方案2】:

由于存在关于非限定名称如何依赖或非限定名称查找如何应用于依赖名称的问题:


跟踪解析:确定我们如何解析语句

如果在模板中遇到依赖名称,则始终假定不是为类型命名,除非适用的名称查找发现它是一个类型或者我们在名称前加上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 解析为指针的声明,但 not 解析为乘法。在最后一条语句中,我们在预解析消歧期间假设 T::name 不是类型并尝试将其解析为表达式。这将失败,因为我们希望在T::name 之后有一个分号或一些运算符。无论名称是否为类型,此查找对后续阶段中名称的含义没有影响:它还不会将名称绑定到任何声明。


实际解析

在我们确定了哪些名称是类型,哪些不是类型之后,我们将实际解析模板。不依赖的名称——即那些没有在依赖的范围内查找或没有被其他规则显式依赖的名称——在模板中使用它们的位置进行查找,它们的含义是不受实例化时可见的任何声明的影响。

依赖于的名称在实例化时会被查找,包括在使用它们的模板定义中以及在其模板被实例化的位置。对于依赖的非限定名称也是如此:

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() 依赖于类型。实例化时,我们将在模板定义周围使用非限定名称查找来查找名为foo 的函数,并在模板定义和实例化点周围使用参数相关查找(大致意思是在T 的命名空间中)它(在main 之后)。然后,依赖于参数的查找将找到foo

如果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 名称中查找foo 期间,我们将在p-&gt; 的范围内(在A&lt;T&gt; 类的范围内)和其中的范围内独立查找出现完整的表达式(这是C&lt;T&gt;::c作为非限定名称的范围),并比较两个名称,如果找到,它们是否指定相同的类型。如果我们在完整表达式的范围内查找时忽略依赖的基类A&lt;T&gt;,我们将在两个基类中找到foo,从而产生歧义。忽略A&lt;T&gt; 意味着我们在查找p-&gt; 时会找到该名称一次,而在查找TypeNameSugar 时会再次找到该名称。

【讨论】:

    【解决方案3】:

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

    问题在于模板类和函数的两个阶段评估的实现。通常标准要求模板被评估两次:

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

    在第一次评估期间,模板参数是未知的,因此无法判断基类将是什么......尤其是它是否包含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
    

    我喜欢 Boost 定义 base_ 内部 typedef 的习惯用法。首先,它肯定有助于定义构造函数,其次,通过明确限定来自 Base 类的内容,它可以让那些遍历代码的人清楚。

    【讨论】:

      猜你喜欢
      • 2017-05-09
      • 2018-11-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-10-26
      • 1970-01-01
      相关资源
      最近更新 更多