为什么内联用户提供的构造函数 odr-uses base class 构造函数?

Why does an inline user-provided constructor odr-uses base class constructors?

考虑以下示例

#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 or VC++编译并执行时,可以看到如下输出:

Initialized!

所有三个编译器都发出 Base<A>::touch 的定义和初始化,而没有发出 Base<B>::touch 的定义和初始化(也通过 godbolt 验证)。所以我会得出结论,这是标准的制裁行为。

对于B的默认构造函数,我们有

[class.ctor]

7 A default constructor that is defaulted and not defined as deleted is implicitly defined when it is odr-used to create an object of its class type ([intro.object]) or when it is explicitly defaulted after its first declaration.

从中可以得出结论,由于这两个条件都不适用于我们的 TU,因此从未隐式定义 B::B()。所以它从不使用 Base<B>::Base()Base<B>::touch。我觉得这很合理。

但是,我无法理解为什么 A::A() 最终会使用其基础 class 的成员。我们知道

[class.mfct]

1 A member function may be defined in its class definition, in which case it is an inline member function ...

[dcl.inline]

6 An inline function or variable shall be defined in every translation unit in which it is odr-used and shall have exactly the same definition in every case ([basic.def.odr]).

[basic.def.odr]

4 ... A constructor for a class is odr-used as specified in [dcl.init].

我们从不初始化任何 A 类型的对象,所以我们不应该使用它的构造函数。所以我们的程序最终不会包含 A::A().

的任何定义

那么为什么它的行为就像存在定义一样?为什么它 odr-use Base<A>::Base() 并导致它的实例化?

这发生在 任何 内联定义中,odr 使用 Base<T>::Base,例如:

inline void x() {
    Base<char>();
}

struct A : Base<A> {
    A(int) {}
    void x() {
        Base<int>();
    }
};

struct C : Base<C> {
    C();
};

inline C::C() = default;  // See [1]

// Will print "Initialized!" four times

B() = default 不 odr-use Base<T>::Base 因为它只被定义为 defaulted. Even though structurally a definition according to [basic.def]/2,因为 [class.ctor]/7 (正如你引用的那样)。只有当 B::B() 本身被 odr 使用时,才会(隐含地)定义 odr-use Base<T>::Base 的定义。

对于定义的其他内联函数,不存在这样的豁免。他们的定义无条件地(和结构上)包含一个 odr-use Base<T>::BaseA::A() 在您的示例中是一个定义,您的程序 确实 包含使用 Base<T>::Base.

A::A() 的定义

“应在使用 odr 的每个翻译单元中定义内联函数或变量”。这是一个“应”要求(对于每个使用内联函数的 TU,都需要一个定义),而不是使用内联函数的 odr 的结果。


[1] 由于 [dcl.fct.def.default]/5:

A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is defined at the point where it is explicitly defaulted

所以 C::C() 有一个默认定义,它会立即生成“隐式”定义。