【问题标题】:Scope resolution in templated inheritance (possibly what is called mixin)模板化继承中的范围解析(可能是所谓的 mixin)
【发布时间】:2019-07-26 00:04:09
【问题描述】:

假设我有模板类

#include <iostream>

class A1 {
public:
  int x{314159};
};

template<typename Context>
class A2 : public Context {};

template<typename Context>
class A3 : public Context {};

template<typename Context>
class A4 : public Context {
public:
  int func() {
    return Context::A1::x;
  }

  int gunc() {
    return this->A1::x;
  }

  int hunc() {
    return A1::x;
  }
};

int main() {
  A4<A3<A2<A1>>> my_A;

  std::cout << "x = func() = " << my_A.func() << std::endl;
  std::cout << "x = gunc() = " << my_A.gunc() << std::endl;
  std::cout << "x = hunc() = " << my_A.hunc() << std::endl;

  return 0;
}

在模板化类A4的定义里面,至少在只使用实例类型A4&lt;A3&lt;A2&lt;A1&gt;&gt;&gt;的情况下,似乎也可以引用x

this->A1::x;

Context::A1::x;

A1::x;

问题 1: 这些是等价的吗?好吧,我想我可以从单独查看的模板类A4 的角度来看它们并不相同。要使Context::A1::x 工作,其模板参数应包含x。要使this-&gt;A1::x 工作,它应该包含一个名为A1 的范围,而该范围又应该包含一个x。对于A1::xA4 的作用域本身应该包含一个名为A1 的作用域,其中包含一个x我的目的是从类型的角度询问它们是否等效 A4&lt;A3&lt;A2&lt;A1&gt;&gt;&gt;

注意事项: 带有-O03 -std=c++17 的 gcc 8.2 在每种情况下都生成相同的汇编代码。也就是说,我只用funcgunchunc函数之一编译了代码,并且只调用了对应的函数,这个编译器产生了相同的可执行文件。当然,严格来说,这并不一定意味着对于抽象语言,这些表达式是等价的。

问题 2:x 范围的“解包”在每种情况下如何工作?也许这个问题没有意义,或者不是我想问的。特别是如果问题 1 的答案是它们是等价的。请允许我在找到有关问题 1 的更多信息后修改此问题,或者先忽略此问题。

问题 2 注释: 这个观察结果可能会阐明为什么我不确定拆包是如何工作的。如果在模板类A4 中我们还有一种方法

int iunc() {
  return Context::Context::A1::x;
}

然后编译失败并显示

memberTemplatedParent.cpp: In instantiation of ‘int A4<Context>::iunc() [with Context = A3<A2<A1> >]’:
memberTemplatedParent.cpp:48:45:   required from here
memberTemplatedParent.cpp:37:22: error: no type named ‘Context’ in ‘class A3<A2<A1> >’
 return Context::Context::A1::x;
                  ^

所以,至少对于gcc,在创建A4 的类型实例的那一刻,其模板参数的模板参数不是有效名称(或者我在@987654350 中没有正确命名) @)。

【问题讨论】:

标签: c++ templates scope-resolution


【解决方案1】:

问题 1 和 2:

所有版本都等同于您选择的实例化。只要不产生歧义,可以不指定范围直接使用成员x。如果成员不在当前类中,则检查基类,等等。

如果您指定了特定的基类并且成员 x 不存在,则再次查询基类。

对于您的特定专业,您有

class A2<A1> : public A1 {};

class A3<A2<A1>> : public A2<A1>{};

class A4<A3<A2<A1>>> : public A3<A2<A1>> {
public:
  int func() {
    return A3<A2<A1>>::A1::x;  // fine: search for x in A1,
                               // where A1 is searched in A3<A2<A1>>
  }
  int gunc() {
     return this->A1::x; // fine: why not specifying A1 directly. The this pointer
                         // is not required here but adding does not cause any harm.
  }
  int hunc() {
     return A1::x; // fine: why not specifying A1 directly.
  }
  int iunc() {
     return x; // fine: would be possible as well
  }

};

最后一个问题:

int iunc() {
  return Context::Context::A1::x;
}

模板实例化后如下

int iunc() {
  return A3<A2<A1>>::Context::A1::x;
}

编译器现在抱怨在类A3&lt;A2&lt;A1&gt;&gt; 中没有typedef,它引入了名称Context。模板参数仅在类模板中可见。

【讨论】:

  • 我明白了。因此,规则是:命名为A1 的范围在每种情况下都从继承的起点开始搜索,直到找到为止。然后在其中搜索x。在A1::x 中,起点是当前范围。在Context::A1::x 中,起点是传递给参数Context 的类型,在我的例子中是A3&lt;A2&lt;A1&gt;&gt; 等。
【解决方案2】:

在这种情况下,我认为您正在进行继承(使用模板)。所以 Context::x 指的是父级的 x 属性。在这种情况下 A3,由于 A3 不会覆盖此属性,因此您与 A1::x 相同。 在第二个(gunc)中,您使用“this”直接引用 A1,因此没有问题。 在第三个(hunc,没有这样使用)中,同样是 gunc,隐式引用 self。 (但我不完全确定)

另外,如果你在 A2 类中添加:

template<typename Context>
class A2 : public Context {
public :
    int x{45678};
};

第一个将打印“45678”

如果现在你在保留 A2 的同时添加 A3

template<typename Context>
class A3 : public Context {
public :
    int x{67890};
};

第一个输出将是 67890

【讨论】:

  • 哦,对不起。我的错。我错过了Context::x 中的A1,以及最后一次呼叫hunc,而不是重复呼叫gunc。但是您的解释有助于我更好地理解这一点。
猜你喜欢
  • 2021-08-09
  • 2012-01-03
  • 1970-01-01
  • 2015-02-04
  • 2013-07-06
  • 1970-01-01
  • 2015-10-02
  • 2015-04-22
  • 1970-01-01
相关资源
最近更新 更多