Consider the following code:
#include <iostream>
struct B {
char i;
B(char i) : i(i) {};
void bar() {};
};
struct D : B {
int y;
D(char i, int y) : B(i), y(y) {};
};
void foo(B *arr, size_t size)
{
for(B *end = arr + size; arr < end; ++arr) {
std::cout << arr->i << std::endl;
}
}
int main()
{
D arr[3] = { {'a', 65}, {'b', 66}, {'c', 67} };
foo(arr, sizeof(arr) / sizeof(*arr));
}
As expected, only a
is printed. Well, a
and two padding bytes that follow i
in base class B
.
Then imagine that we make B
‘s member function bar
virtual. That will make both classes polymorphic. In this configuration the program would output abc
in both clang and gcc. Which means that they both calculate offsets based on polymorphic types and some runtime info. Which is noncense, as fat as I am concerned.
I also tried to add another derived class with different layout:
struct C : B {
long y;
C(char i, long y) : B(i), y(y) {};
};
//...
C rra[] = { {'a', 65}, {'b', 66}, {'c', 67} };
foo(rra, sizeof(rra) / sizeof(*rra));
In my case the output was a bizzare apB
, which is not what I initialized those with. So it seems that no runtime info is used to calculate the offsets.
So, my question is pretty my staightforward:
- When iterating over an array of derived via a pointer to a base in a polymorphic context, which offset will be used according to the standard?
I sifted through the standard and found no mention of runtime info affecting the offsets.
[expr.add] doesn’t really clarifies the matter. Techically it says that the resulting pointer shall point at the element of an array.
It becomes really odd for me when I make the foo
print:
#include <iostream>
struct B {
char i;
B(char i) : i(i) {};
virtual void foo() { std::cout << "I AM BASE" << i << std::endl; };
};
struct D : B {
int y;
D(char i, int y) : B(i), y(y) {};
virtual void foo() { std::cout << "I AM DERIVED" << i << std::endl; };
};
struct C : B {
long y;
C(char i, long y) : B(i), y(y) {};
virtual void foo() { std::cout << "I AM CERIVED" << i << std::endl; };
};
void foo(B *arr, size_t size)
{
for(B *end = arr + size; arr < end; ++arr) {
std::cout << arr->i << std::endl;
arr->foo();
}
}
int main()
{
D arr[] = { {'a', 65}, {'d', 66}, {'c', 67} };
foo(arr, sizeof(arr) / sizeof(*arr));
C rra[] = { {'a', 70}, {'d', 66}, {'c', 67} };
foo(rra, sizeof(rra) / sizeof(*rra));
}
It prints I AM DERIVED
with correct char
for the first iteration, and only one CERIVED
for the second one, then fails with SIGSEGV
.
I can reproduce it with both latest and gcc. The link to godbolt.