I am writing a math vector and matrix library for practice, and I use a crtp base class in order to not have to define several functions more than once. I also inherit a static array class. Depending on the order of the inherited classes however I get a different size for my structure in msvc. Is this a bug, or is this something I should have expected? You can find a live demo at: https://godbolt.org/z/3EPVI5 Note that the same compiles just fine in gcc. The code for reference:
template<typename D>
struct crtp
{
};
template<typename T, int s>
struct arr_impl
{
T e[s];
};
template<typename T, int s>
struct vc : public arr_impl<T, s>, public crtp<vc<T, s>>
{
};
template<typename T, int s>
struct vc2 : public crtp<vc2<T, s>>, public arr_impl<T, s>
{
};
int main()
{
static_assert(sizeof(vc<vc<float,3>,2>) == 24);
static_assert(sizeof(vc2<vc2<float,3>,2>) == 24);
return 0;
}
I've narrowed it further, see: https://godbolt.org/z/tGCn_J As it seems only the nesting is required and an empty class:
struct empty_struct{};
template<typename T>
struct st
{
T a;
};
template<typename T>
struct vc : public empty_struct, public st<T> {};
template<typename T>
struct vc2 : public st<T>, public empty_struct{};
int main()
{
static_assert(sizeof(vc<vc<float>>) == 4);
static_assert(sizeof(vc2<vc2<float>>) == 4);
return 0;
}
I believe MSVC conforms to the C++17 standard in this regard.
From [intro.object] (emphasis mine):
Unless it is a bit-field, a most derived object shall have a nonzero size and shall occupy one or more bytes of storage. Base class subobjects may have zero size. An object of trivially copyable or standard-layout type shall occupy contiguous bytes of storage.
That's really all the C++17 standard has to say on the matter. The empty base optimization is totally optional. The standard just says it's legal, not that it must be implemented, or in what situations it should be implemented.
The current draft of the C++20 standard is a bit more prescriptive.
From [intro.object] (emphasis again mine)
An object has nonzero size if it
-- is not a potentially-overlapping subobject, or
-- is not of class type, or
-- is of a class type with virtual member functions or virtual base classes, or
-- has subobjects of nonzero size or bit-fields of nonzero length.
Otherwise, if the object is a base class subobject of a standard-layout class type with no non-static data members, it has zero size. Otherwise, the circumstances under which the object has zero size are implementation-defined. Unless it is a bit-field, an object with nonzero size shall occupy one or more bytes of storage, including every byte that is occupied in full or in part by any of its subobjects. An object of trivially copyable or standard-layout type ([basic.types]) shall occupy contiguous bytes of storage.
So under C++20 your base class would be guaranteed to have zero size since it is an empty base class of a standard-layout class.