LINUX.ORG.RU

История изменений

Исправление 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;
}