История изменений
Исправление www_linux_org_ru, (текущая версия) :
ок, говорю разгадку — в юнион надо положить existentials (вроде даже количество звездочек совпадает с тем, что я нарисовал от руки)
собственно, это решает вопрос — внутри existential надо сделать поле данных и поле функции, которая работает именно с этими данными; снаружи разрешить вызывать только эту функцию
ниже прога (правда, требует с++0х из-за unrestricted union); вопрос к читателям — что *опасного* можно сделать с объектом типа A1_or_A2 *после* того, как он инициализирован? по-моему, без нарушения типов с помощью reinterpret_cast и юнионов, отличных от А1_or_A2 ничего опасного не сделать
тут очевидно, что типизация в виде тайптега или че-то подобного нигде не хранится, но, как подсказал анонимус, въедливым читателям придется это доказывать; скажем, если бы в качестве аргумента вместо repeat_head1/repeat_head_twice2 использовался бы print1/print2, то можно было бы спорить на эту тему, т.к. в рантайме, имея указатель u.a1.function и доступ к сегменту кода, можно было бы проанализировать код function и определить количество разыменований (1 или 2 штуки) и так сделать вывод о его типе данных
именно поэтому function инициализируется не принтами, а параметрически полиморфной фигней, из кода которой тип данных не вытащищь — она прекрасно работает со всеми
а теперь код (по-моему, длинные строки нагляднее макросов, а шаблоны я не хотел юзать, чтобы было можно убедиться в одинаковости кода):
#include <iostream>
#include <assert.h>
#include <stdlib.h>
typedef double dbl;
typedef double* dbl_ptr;
int* new1(int x) { return new int(x); }
dbl** new2(dbl x) { return new dbl_ptr( new dbl(x) ); }
struct Node1 { int* element; Node1* next; Node1( int e, Node1* n ): element(new1(e)), next(n) {} };
struct Node2 { dbl** element; Node2* next; Node2( dbl e, Node2* n ): element(new2(e)), next(n) {} };
/// ^^ существенная разница
/// данные 2 функции не изменяют список, если он пустой, и повторяют голову списка, если он не пустой
/// пример: [1,2,3] преобразуется в [1,1,2,3]
/// кроме того, у данных 2 функций полностью одинаков бинарный код
Node1* repeat_head1(Node1* arg) { if(!arg) return arg; Node1* node=new Node1(*arg); node->next=arg; return node; }
Node2* repeat_head2(Node2* arg) { if(!arg) return arg; Node2* node=new Node2(*arg); node->next=arg; return node; }
Node1* repeat_head_twice1(Node1* arg) { return repeat_head1(repeat_head1(arg)); }
Node2* repeat_head_twice2(Node2* arg) { return repeat_head2(repeat_head2(arg)); }
/// возвращаемое значение, а не void, сделано для того, чтобы можно было заюзать эти функции вместо repeat_head
Node1* print1(Node1* node) { if(!node) { std::cout << '\n'; }else{ std::cout << *(node->element) << ' '; print1(node->next); } return node; }
Node2* print2(Node2* node) { if(!node) { std::cout << '\n'; }else{ std::cout << **(node->element) << ' '; print2(node->next); } return node; }
/// ^^ существенная разница
Node1* a1_d; Node1* (*a1_f)(Node1*);
Node2* a2_d; Node2* (*a2_f)(Node2*);
class A1 { Node1* data; Node1* (*function)(Node1*); public: void init(){ data=a1_d; function=a1_f; } void do_something(){ if(data && function) data=function(data); } };
class A2 { Node2* data; Node2* (*function)(Node2*); public: void init(){ data=a2_d; function=a2_f; } void do_something(){ if(data && function) data=function(data); } };
union A1_or_A2 {
A1 a1;
A2 a2;
};
void typesafe( A1_or_A2& a1_or_a2 ) {
a1_or_a2.a1.do_something();
/// a1_or_a2.a2.do_something(); <--- так тоже можно, и результат будет тот же
}
int main()
{
/// проверяем, что в А1 и А2 нет скрытых полей с тайптегом
assert( sizeof(A1)==2*sizeof(void*) );
assert( sizeof(A2)==2*sizeof(void*) );
assert( sizeof(int*)==sizeof(int) );
assert( sizeof(dbl*)==sizeof(int) );
Node1* ptr1 = new Node1( 1, new Node1( 11, NULL ) );
Node2* ptr2 = new Node2( 2, new Node2( 22, new Node2( 222, NULL ) ) );
A1 a1; a1_d=ptr1; a1_f = repeat_head1 ; a1.init();
A2 a2; a2_d=ptr2; a2_f = repeat_head_twice2 ; a2.init();
print1( *reinterpret_cast<Node1**>(&a1) );
print2( *reinterpret_cast<Node2**>(&a2) );
A1_or_A2 u; /// тут оно пока что не инициализировано
u.a1=a1;
typesafe(u);
print1( *reinterpret_cast<Node1**>(&u) );
u.a2=a2;
typesafe(u);
print2( *reinterpret_cast<Node2**>(&u) );
#ifdef AA1
/// для тех, кто будет утверждать, что А1 и А2 это один и тот же тип -- расскомментируйте следующую строку и получите сегфолт:
u.a1=a1;
print2( *reinterpret_cast<Node2**>(&u) );
#endif
#ifdef AA2
/// при этом следующие строки хотя и выдают бред, но сегфолт не вызывают:
u.a2=a2;
print1( *reinterpret_cast<Node1**>(&u) );
#endif
return 0;
}
Исходная версия www_linux_org_ru, :
ок, говорю разгадку — в юнион надо положить existentials (вроде даже количество звездочек совпадает с тем, что я нарисовал от руки)
собственно, это решает вопрос — внутри existential надо сделать поле данных и поле функции, которая работает именно с этими данными; снаружи разрешить вызывать только эту функцию
ниже прога (правда, требует с++0х из-за unrestricted union); вопрос к читателям — что *опасного* можно сделать с объектом типа A1_or_A2 *после* того, как он инициализирован? по-моему ничего
тут очевидно, что типизация в виде тайптега или че-то подобного нигде не хранится, но, как подсказал анонимус, въедливым читателям придется это доказывать; скажем, если бы в качестве аргумента вместо repeat_head1/repeat_head_twice2 использовался бы print1/print2, то можно было бы спорить на эту тему, т.к. в рантайме, имея указатель u.a1.function и доступ к сегменту кода, можно было бы проанализировать код function и определить количество разыменований (1 или 2 штуки) и так сделать вывод о его типе данных
именно поэтому function инициализируется не принтами, а параметрически полиморфной фигней, из кода которой тип данных не вытащищь — она прекрасно работает со всеми
а теперь код (по-моему, длинные строки нагляднее макросов, а шаблоны я не хотел юзать, чтобы было можно убедиться в одинаковости кода):
#include <iostream>
#include <assert.h>
#include <stdlib.h>
typedef double dbl;
typedef double* dbl_ptr;
int* new1(int x) { return new int(x); }
dbl** new2(dbl x) { return new dbl_ptr( new dbl(x) ); }
struct Node1 { int* element; Node1* next; Node1( int e, Node1* n ): element(new1(e)), next(n) {} };
struct Node2 { dbl** element; Node2* next; Node2( dbl e, Node2* n ): element(new2(e)), next(n) {} };
/// ^^ существенная разница
/// данные 2 функции не изменяют список, если он пустой, и повторяют голову списка, если он не пустой
/// пример: [1,2,3] преобразуется в [1,1,2,3]
/// кроме того, у данных 2 функций полностью одинаков бинарный код
Node1* repeat_head1(Node1* arg) { if(!arg) return arg; Node1* node=new Node1(*arg); node->next=arg; return node; }
Node2* repeat_head2(Node2* arg) { if(!arg) return arg; Node2* node=new Node2(*arg); node->next=arg; return node; }
Node1* repeat_head_twice1(Node1* arg) { return repeat_head1(repeat_head1(arg)); }
Node2* repeat_head_twice2(Node2* arg) { return repeat_head2(repeat_head2(arg)); }
/// возвращаемое значение, а не void, сделано для того, чтобы можно было заюзать эти функции вместо repeat_head
Node1* print1(Node1* node) { if(!node) { std::cout << '\n'; }else{ std::cout << *(node->element) << ' '; print1(node->next); } return node; }
Node2* print2(Node2* node) { if(!node) { std::cout << '\n'; }else{ std::cout << **(node->element) << ' '; print2(node->next); } return node; }
/// ^^ существенная разница
Node1* a1_d; Node1* (*a1_f)(Node1*);
Node2* a2_d; Node2* (*a2_f)(Node2*);
class A1 { Node1* data; Node1* (*function)(Node1*); public: void init(){ data=a1_d; function=a1_f; } void do_something(){ if(data && function) data=function(data); } };
class A2 { Node2* data; Node2* (*function)(Node2*); public: void init(){ data=a2_d; function=a2_f; } void do_something(){ if(data && function) data=function(data); } };
union A1_or_A2 {
A1 a1;
A2 a2;
};
void typesafe( A1_or_A2& a1_or_a2 ) {
a1_or_a2.a1.do_something();
/// a1_or_a2.a2.do_something(); <--- так тоже можно, и результат будет тот же
}
int main()
{
/// проверяем, что в А1 и А2 нет скрытых полей с тайптегом
assert( sizeof(A1)==2*sizeof(void*) );
assert( sizeof(A2)==2*sizeof(void*) );
assert( sizeof(int*)==sizeof(int) );
assert( sizeof(dbl*)==sizeof(int) );
Node1* ptr1 = new Node1( 1, new Node1( 11, NULL ) );
Node2* ptr2 = new Node2( 2, new Node2( 22, new Node2( 222, NULL ) ) );
A1 a1; a1_d=ptr1; a1_f = repeat_head1 ; a1.init();
A2 a2; a2_d=ptr2; a2_f = repeat_head_twice2 ; a2.init();
print1( *reinterpret_cast<Node1**>(&a1) );
print2( *reinterpret_cast<Node2**>(&a2) );
A1_or_A2 u; /// тут оно пока что не инициализировано
u.a1=a1;
typesafe(u);
print1( *reinterpret_cast<Node1**>(&u) );
u.a2=a2;
typesafe(u);
print2( *reinterpret_cast<Node2**>(&u) );
#ifdef AA1
/// для тех, кто будет утверждать, что А1 и А2 это один и тот же тип -- расскомментируйте следующую строку и получите сегфолт:
u.a1=a1;
print2( *reinterpret_cast<Node2**>(&u) );
#endif
#ifdef AA2
/// при этом следующие строки хотя и выдают бред, но сегфолт не вызывают:
u.a2=a2;
print1( *reinterpret_cast<Node1**>(&u) );
#endif
return 0;
}