模板化继承中的范围解析(可能称为 mixin)

Scope resolution in templated inheritance (possibly what is called mixin)

本文关键字:mixin 继承 范围      更新时间:2023-10-16

>假设我有模板化类

#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<A3<A2<A1>>>时,似乎可以将x称为

this->A1::x;

Context::A1::x;

A1::x;

问题1:这些是等价的吗?好吧,我想我可以看到它们从模板化类的角度来看并不等同A4孤立地看待。要使Context::A1::x工作,其模板参数应包含x。为了使this->A1::x工作,它应该包含一个名为A1的范围,而 又应该包含一个x。为了使A1::x工作,A4本身的范围应该包含一个名为A1包含x我的目的是从类型的角度来看问它们是否等效A4<A3<A2<A1>>>.

注意:带有-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;
^

因此,至少在创建A4的类型实例的那一刻gcc,其模板参数的模板参数不是有效名称(或者我没有在Context::Context::A1::x中正确命名它)。

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

另外,如果您添加 A2 类:

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

第一个将打印"45678">

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

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

第一个输出将是 67890

问题 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<A2<A1>>中没有 typedef 引入名称Context。模板参数仅在类模板中可见。