0

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;
}
lightxbulb
  • 1,251
  • 12
  • 29
  • Unrelated to your problem, but array sizes can never be negative, so please use `size_t` (an unsigned type) for the array size template argument. – Some programmer dude Jul 20 '19 at 22:19
  • @Someprogrammerdude I use a static assert for that, since I want my size() method to return int, and I want my indexing to use int. openmp doesn't play nice with unsigned integer loops for some reason, and I do not want to get uint to int warnings. – lightxbulb Jul 20 '19 at 22:20
  • That could be very useful information and as such should be in the main question itself. And probably a description of the actual problem you're trying to solve. Why are you doing something like you show? What is it supposed to solve? – Some programmer dude Jul 20 '19 at 22:36
  • 1
    @Someprogrammerdude How would this help with regards to answering whether this is a bug, or behaviour that was left to the implementation? What problem I am solving is unrelated to whether this is a bug. I just want to file a bug report with ms if this is indeed a compiler bug, since it works just fine both on clang and gcc. – lightxbulb Jul 20 '19 at 22:38
  • Because if it turns out to be a bug in your code (and not the compiler or an implementation detail) it doesn't really help you much to fix the underlying problem. That means you might have to come back again asking about it, and showing the same code ("show us your attempt"). And if it's a bug in the compiler or an implementation detail, you might also have to come back asking for workarounds (if you want to be portable) and still have to give us those details. If you ask about your actual problem first, then we might be able to help you quicker. – Some programmer dude Jul 20 '19 at 23:01
  • 1
    @Someprogrammerdude You have the minimal code here, so you can decide whether it's a bug in my code or not - feel free to play with what I have provided. Thank you for the suggestion, but I prefer to keep separate questions in different threads - this thread is regarding whether the above code snippet fails to compile due to a compiler bug. The obvious workaround in my original code would be just to swap the order and everything would work fine. However I want to know if the above does not work because it's UB in the standard, or whether it's a compiler bug on MS's part. – lightxbulb Jul 20 '19 at 23:04
  • 1
    MSVC is know to be bad at performing Empty Base Optimization. To ask it to perform EBO, add `__declspec(empty_bases)`: `struct __declspec(empty_bases) vc2`. – cpplearner Jul 20 '19 at 23:11
  • @cpplearner So is the resulting size in this case not determined by the rules in the standard, and is instead left up to the implementation? – lightxbulb Jul 20 '19 at 23:15

1 Answers1

2

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.

Miles Budnek
  • 28,216
  • 2
  • 35
  • 52