【发布时间】:2021-01-23 16:32:03
【问题描述】:
考虑以下说明性示例
#include <iostream>
template <typename>
struct Base {
static int const touch;
Base() {
(void)touch;
}
};
template<typename CRTP>
int const Base<CRTP>::touch = []{
std::cout << "Initialized!\n";
return 0;
}();
struct A : Base<A> {
A() {}
};
struct B : Base<B> {
B() = default;
};
int main() {
}
当上述程序被GCC、Clang或VC++编译并执行时,始终看到如下输出:
Initialized!
所有三个编译器都发出Base<A>::touch 的定义和初始化,而都没有发出Base<B>::touch 的定义和初始化(也通过godbolt 验证)。所以我会得出结论,这是标准的受制裁行为。
对于B的默认构造函数,我们有
[class.ctor]
7 默认构造函数被默认且未定义为已删除,当它被用于创建其类类型 ([intro.object]) 的对象时,或者当它在其第一个之后显式默认时,它被隐式定义声明。
从中可以得出结论,由于这两个条件都不适用于我们的 TU,因此永远不会隐式定义 B::B()。所以它永远不会使用 Base<B>::Base() 和 Base<B>::touch。我觉得这很合理。
但是,我不明白为什么A::A() 最终会使用其基类的成员进行 odr。我们知道
[class.mfct]
1 成员函数可以在其类定义中定义,在这种情况下它是一个内联成员函数...
[dcl.inline]
6 内联函数或变量应在使用 odr 的每个翻译单元中定义,并且在每种情况下都应具有完全相同的定义 ([basic.def.odr])。
[basic.def.odr]
4 ... 类的构造函数按照 [dcl.init] 中的规定进行 odr-used。
我们从不初始化任何类型为A 的对象,因此我们不应该使用它的构造函数来进行odr。所以我们的程序最终不会包含任何A::A() 的定义。
那么为什么它表现得好像存在定义一样?为什么它使用 Base<A>::Base() 并导致其实例化?
【问题讨论】:
标签: c++ templates language-lawyer one-definition-rule