Consider the following illustrative example
#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() {
}
When the above program is compiled by GCC, Clang or VC++ and executed, one consistently sees the following output:
Initialized!
All three compilers emit the definition and initialization of Base<A>::touch, whereas neither emits the definition and initialization of Base<B>::touch (verified also via godbolt). So I would conclude this is standard sanctioned behavior.
For the defaulted constructor of B, we have
[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.
From which one may conclude that since neither condition applies in our TU, B::B() is never implicitly defined. So it never odr-uses Base<B>::Base() and Base<B>::touch. I find this reasonable.
However, I cannot understand why A::A() ends up odr-using the members of its base class. We know that
[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].
We never initialize any objects of type A, so we shouldn't be odr-using its constructor. And so our program wouldn't end up containing any definition of A::A().
So why does it behave as though a definition exists? Why does it odr-use Base<A>::Base() and causes its instantiation?