История изменений
Исправление quasimoto, (текущая версия) :
sizeof(int*) == sizeof(int) и sizeof(double*) == sizeof(int) - кстати, не факт (x86_64/Linux, например).
Попробуй добавить отладочную печать в оба метода do_something, тогда станет видно, что оба раза вызывается только один из них, в зависимости от того какой вариант выбран в typesafe.
Это случай если не одинаковых типов, то типов с одинаковым sizeof и memory layout - тут просто везёт. Возьмём действительно разные типы:
struct A1 {
Node1* data;
Node1* data2;
Node1*(*fn)(Node1*);
void do_something() { puts("A1 case"); if (data && fn) data = fn(data); if (data2 && fn) data2 = fn(data2); } };
struct A2 {
Node2* data;
Node2*(*fn)(Node2*);
void do_something() { puts("A2 case"); if (data && fn) data = fn(data); }
};
union A1_or_A2 { A1 a1; A2 a2; };
безопасными будут все (типично) безопасные использования объединений:
printf("%lu, %lu\n", sizeof(A1), sizeof(A2));
A1 a1 = { new Node1(1, new Node1(11, NULL)), new Node1(3, new Node1(33, NULL)), repeat_head1 };
A2 a2 = { new Node2(2, new Node2(22, new Node2(222, NULL))), repeat_head_twice2 };
A1_or_A2 u;
u.a1 = a1;
u.a1.do_something(); // A1 case
print1(u.a1.data);
print1(u.a1.data2);
u.a2 = a2;
u.a2.do_something(); // A2 case
print2(u.a2.data);
теперь если инициализировать u одним значением (a1 или a2), но вызвать метод по другому полю (u.a2.do_something() или u.a1.do_something(), соответственно), то что произойдет - метод класса A2 (или A1) будет вызван на объекте класса A1 (или A2), то есть он будет искать значения полей по смещениям одного класса в совсем другом классе:
u.a1 = a1;
u.a2.do_something(); // A2 case + segfault!
print1(u.a1.data);
print1(u.a1.data2);
u.a2 = a2;
u.a1.do_something(); // A1 case
print2(u.a2.data);
в первом случае вполне может быть сегфолт, во втором, опять, просто везёт.
То есть, тот typesafe можно написать двумя вариантами и разницы между ними нет только при A == B в смысле memory layout. Что касается
u.a1 = a1;
print2(u.a2.data);
u.a2 = a2;
print1(u.a1.data);
то в первом случае мы лезем в объект класса A1 используя смещение из класса A2 и нам везёт (для data), но потом print2 пытается печатать список указателей на int как список указателей на указатели на double, то есть получается лишнее разыменование и всё падает. Во втором случае нам опять везёт с data, а print1 успешно печатает список **double как список *int, то есть разыменовывая как (int)(*(*int)((**double)x)), _если_ sizeof(**double) <= sizeof(*int) <= sizeof(int) (или для малых адресов), то мы увидим не сами doublы а те адреса по которым они лежат.
Исходная версия quasimoto, :
sizeof(int*) == sizeof(int) и sizeof(double*) == sizeof(int) - кстати, не факт (x86_64/Linux, например).
Попробуй добавить отладочную печать в оба метода do_something, тогда станет видно, что оба раза вызывается только один из них, в зависимости от того какой вариант выбран в typesafe.
Это случай если не одинаковых типов, то типов с одинаковым sizeof и memory layout - тут просто везёт. Возьмём действительно разные типы:
struct A1 {
Node1* data;
Node1* data2;
Node1*(*fn)(Node1*);
void do_something() { puts("A1 case"); if (data && fn) data = fn(data); if (data2 && fn) data2 = fn(data2); } };
struct A2 {
Node2* data;
Node2*(*fn)(Node2*);
void do_something() { puts("A2 case"); if (data && fn) data = fn(data); }
};
union A1_or_A2 { A1 a1; A2 a2; };
безопасными будут все (типично) безопасные использования объединений:
printf("%lu, %lu\n", sizeof(A1), sizeof(A2));
A1 a1 = { new Node1(1, new Node1(11, NULL)), new Node1(3, new Node1(33, NULL)), repeat_head1 };
A2 a2 = { new Node2(2, new Node2(22, new Node2(222, NULL))), repeat_head_twice2 };
A1_or_A2 u;
u.a1 = a1;
u.a1.do_something(); // A1 case
print1(u.a1.data);
print1(u.a1.data2);
u.a2 = a2;
u.a2.do_something(); // A2 case
print2(u.a2.data);
теперь если инициализировать u одним значением (a1 или a2), но вызвать метод по другому полю (u.a2.do_something() или u.a1.do_something(), соответственно), то что произойдет - метод класса A2 (или A1) будет вызван на объекте класса A1 (или A2), то есть он будет искать значения полей по смещениям одного класса в совсем другом классе:
u.a1 = a1;
u.a2.do_something(); // A2 case + segfault!
print1(u.a1.data);
print1(u.a1.data2);
u.a2 = a2;
u.a1.do_something(); // A1 case
print2(u.a2.data);
в первом случае вполне может быть сегфолт, во втором, опять, просто везёт.
То есть, тот typesafe можно написать двумя вариантами и разницы между ними нет только при A == B в смысле memory layout. Что касается
u.a1 = a1;
print2(u.a2.data);
u.a2 = a2;
print1(u.a1.data);
то в первом случае мы лезем в объект класса A1 используя смещение из класса A2 и нам везёт (для data), но потом print2 пытается печатать список указателей на int как список указателей на указатели на double, то есть получается лишнее разыменование и всё падает. Во втором случае нам опять везёт с data, а print1 успешно печатает список **double как список *int, то есть разыменовывая как (int)(*(*int)((**double)x)), _если_ sizeof(**double) == sizeof(*int) == sizeof(int) (или для малых адресов), то мы увидим не сами doublы а те адреса по которым они лежат.