Avatar billede stormy Nybegynder
24. november 2004 - 17:56 Der er 4 kommentarer

Advanceret template programmering ( double dispatch )

Hej eksperter !

Jeg er i gang med at konstruere et template-bibliotek til script-sprog. Disse vil indeholde forskellige typer, men da jeg gerne vil sikre bedst mulig udførselshastighed, har jeg besluttet at for foruddefinerede typer skal evaulering af udtryk med disse, bruge parametriserede templates.

Men jeg er løbet lidt ind i et "problem", da jeg gik i gang med at lave funktionaliteten for binære operationer (to operander, ikke bit operationer). Mit problem optræder i funktionen GSL_ExpressionT<T>::Unify - hvor jeg vha. makroen TEST_TYPE bruger RTTI til at afgøre typen af anden operand. Jeg er interesseret i at høre om der er nogen her der kender (kan finde frem til) en måde at udføre denne funktionalitet vha. klassisk double dispatch ? At komme fra dynamisk typeinformation til statisk typeinformation er kernen i dette problem, er dette muligt ?
Unify gør jo jobbet glimrende for første operand, men for begge operander ?

Derudover er jeg interesseret i kommentarer til denne fremgangsmåde + evt. problemer jeg kunne støde på senere hen.

Jeg har barberet min kode ned, til relativt simpel eksempel (2 typer og 4 operationer) - men samtidig holdt den så den kan oversættes.

// Hold on - this definitely isn't Kansas any more :

#include <iostream>
#include <cassert>

enum binary_operator { binop_add, binop_sub,
              binop_mul, binop_div };

template<binary_operator OP, typename T>
class eval_functor
{
  public:
  T operator() ( T const& op1, T const& op2 )
  { assert(false); }
};

#define EVAL_FUNCTOR(OPID,OP)                              \
  template<typename T>                                    \
  class eval_functor<OPID,T>                              \
  {                                                        \
    public:                                                \
    T operator() (T const & op1, T const & op2 )          \
    { return op1 OP op2; }                                \
  }

EVAL_FUNCTOR(binop_add,+);
EVAL_FUNCTOR(binop_sub,-);
EVAL_FUNCTOR(binop_mul,*);
EVAL_FUNCTOR(binop_div,/);

class GSL_Expression
{
  public:
  GSL_Expression()
  { }
  virtual ~GSL_Expression()
  { }
  virtual GSL_Expression * Unify( GSL_Expression * second, binary_operator op ) = 0;
  virtual void Output( std::ostream& ) const = 0;
};

std::ostream & operator << ( std::ostream & os, GSL_Expression const & e )
{
  e.Output(os);
  return os;
}

// Forward declaration :
template<typename T1, typename T2>
class Unification;

#define TEST_TYPE(type)                              \
  GSL_ExpressionT<type> * test ## type =            \
    dynamic_cast<GSL_ExpressionT<type> *>(second);  \
  if( test ## type )                                \
  {                                                  \
    Unification<T,type> unifier;                    \
    return unifier( this, test ## type, op );        \
  }

template<typename T>
class GSL_ExpressionT : public GSL_Expression
{
  public:
    GSL_ExpressionT()
      : GSL_Expression()
    { }
    virtual ~GSL_ExpressionT()
    { }
    virtual GSL_Expression * Unify( GSL_Expression * second, binary_operator op )
    {
      TEST_TYPE(int)
      TEST_TYPE(double)
      assert(false);
    }
    virtual void Output( std::ostream& os) const
    {
      os << this->evaluate();
    }
    virtual T evaluate( void ) const = 0;
};

template<typename T>
class GSL_PrimaryValue : public GSL_ExpressionT<T>
{
  private:
    T primary;
  public:
    GSL_PrimaryValue( const T& init )
      : GSL_ExpressionT<T>(), primary(init)
    { }
    virtual ~GSL_PrimaryValue()
    { }
    virtual T evaluate( void ) const
    { return primary; }
};

template<typename T, binary_operator OP>
class GSL_Binary : public GSL_ExpressionT<T>
{
  private:
    GSL_ExpressionT<T> * op1, * op2;
  public:
    GSL_Binary( GSL_ExpressionT<T> * lhs,
                GSL_ExpressionT<T> * rhs )
      : GSL_ExpressionT<T>(), op1(lhs), op2(rhs)
    { }
    virtual ~GSL_Binary()
    {
      delete op1;
      delete op2;
    }

    virtual T evaluate( void ) const
    {
      eval_functor<OP,T> f;
      return f( op1->evaluate(), op2->evaluate() );
    }
};

template<typename T_DESTINATION, typename T_SOURCE>
class GSL_Cast : public GSL_ExpressionT<T_DESTINATION>
{
  private:
    GSL_ExpressionT<T_SOURCE> * op;
  public:
    GSL_Cast( GSL_ExpressionT<T_SOURCE> * src )
      : GSL_ExpressionT<T_DESTINATION>(), op(src)
    { }
    virtual ~GSL_Cast()
    {
      delete op;
    }
    virtual T_DESTINATION evaluate( void ) const
    {
      return static_cast<T_DESTINATION>(op->evaluate());
    }
};

template<typename T_DESTINATION, typename T_SOURCE>
class cast_functor
{
  public:
  GSL_ExpressionT<T_DESTINATION> * operator() ( GSL_ExpressionT<T_SOURCE> * src )
  {
    return new GSL_Cast<T_DESTINATION,T_SOURCE>( src );
  }
};

// Partial specialization to ensure that only needed cast objects are created :
template<typename T>
class cast_functor<T,T>
{
  public:
  GSL_ExpressionT<T> * operator() ( GSL_ExpressionT<T> * src )
  {
    return src;
  }
};


template<typename T1, typename T2>
class Promotion;

template<typename T>
class Promotion<T,T>
{
  public:
  typedef T ResultType;
};

#define MK_PROMOTION(T1,T2,Tr)              \
  template<> class Promotion<T1, T2> {      \
    public:                                \
    typedef Tr ResultType;                  \
  };                                        \
                                            \
  template<> class Promotion<T2, T1> {      \
    public:                                \
    typedef Tr ResultType;                  \
  };

MK_PROMOTION( int , double, double )

template<typename T1, typename T2>
class Unification
{
  private :
  typedef typename Promotion<T1,T2>::ResultType ResultType;
  public :
  GSL_Expression * operator() ( GSL_ExpressionT<T1> *init_lhs,
                                GSL_ExpressionT<T2> *init_rhs,
                binary_operator      op)
  {
    cast_functor<ResultType,T1> lhs_f;
    GSL_ExpressionT<ResultType> *lhs = lhs_f( init_lhs );
    cast_functor<ResultType,T2> rhs_f;
    GSL_ExpressionT<ResultType> *rhs = rhs_f( init_rhs );

    switch( op )
    {
      case binop_add : return new GSL_Binary<ResultType,binop_add> (lhs,rhs);
      case binop_sub : return new GSL_Binary<ResultType,binop_sub> (lhs,rhs);
      case binop_mul : return new GSL_Binary<ResultType,binop_mul> (lhs,rhs);
      case binop_div : return new GSL_Binary<ResultType,binop_div> (lhs,rhs);
      default :
        break;
    }
    assert(false);
  }
};

inline GSL_Expression * Binary_Expr( GSL_Expression * lhs,
                                    GSL_Expression * rhs,
                                    binary_operator  op)
{
  return lhs->Unify(rhs,op);
}

int main( int argc, char *argv[])
{
  GSL_Expression * e1 = Binary_Expr(new GSL_PrimaryValue<int>(5),
                                    new GSL_PrimaryValue<int>(8),
                                    binop_mul);

  std::cout << "The result is : " << *e1 << std::endl;

  GSL_Expression * e2 = Binary_Expr(new GSL_PrimaryValue<double>(2.1),
                                    new GSL_PrimaryValue<double>(4.7),
                    binop_add);

  std::cout << "The result is : " << *e2 << std::endl;

  GSL_Expression * e3 = Binary_Expr(e1,e2,binop_sub);

  std::cout << "The result is : " << *e3 << std::endl;

  return 0;
}
Avatar billede segmose Nybegynder
06. december 2004 - 17:24 #1
#define TEST_TYPE(type)                              \
  GSL_ExpressionT<type> * test ## type =            \
    dynamic_cast<GSL_ExpressionT<type> *>(second);  \
  if( test ## type )                                \
  {                                                  \
    Unification<T,type> unifier;                    \
    return unifier( this, test ## type, op );        \
  }


hmm jeg har lidt svært ved at overskue det,men da ingen andre har noget at sige vil jeg stikke næsen frem og forsøge mig.

Hvorfor går dette galt hvis nedenstående sættes in istedet for de 2 gange TEST_TYPE? (uden at have testet det) hvis second kan laves om til templatens T går det så godt.
  GSL_ExpressionT<T> * test = dynamic_cast<GSL_ExpressionT<T> *>(second);
  // can man lave en T ud af second
  if( test ) {
    Unification<T, second> unifier;
    return unifier( this, test , op );
  }
Avatar billede stormy Nybegynder
06. december 2004 - 18:00 #2
Unification<T, second> unifier;

Er ikke nogen lovlig specifikation, da second er en GSL_Expression (ukendt type)..

Det jeg får ud af min makro er :

GSL_ExpressionT<type> * testint = dynamic_cast<GSL_ExpressionT<int> *>(second);
if( testint )
{
  Unification<T,int> unifier;
  return unifier( this, testint, op );
}

GSL_ExpressionT<type> * testdouble = dynamic_cast<GSL_ExpressionT<double> *>(second); 
if( testdouble )
{
  Unification<T,double> unifier;
  return unifier( this, testdouble, op );
}

Den type T, som jeg allerede har - er typen på første operand, som vha. det virtuelle kald til Unify er blevet bestemt. Det jeg leder efter er en metode til at lave noget tilsvarende med andet argument.. Men p.t. er jeg nød til at checke vha. RTTI (Runtime type information) - med kald til dynamic_cast for hver potentiel type for andet argument.

Håber dette hjælper på din forståelse af mit spørgsmål !
Avatar billede stormy Nybegynder
06. december 2004 - 18:11 #3
Glemte en substitution af type:

GSL_ExpressionT<int> * testint = dynamic_cast<GSL_ExpressionT<int> *>(second);
if( testint )
{
  Unification<T,int> unifier;
  return unifier( this, testint, op );
}

GSL_ExpressionT<double> * testdouble = dynamic_cast<GSL_ExpressionT<double> *>(second);
if( testdouble )
{
  Unification<T,double> unifier;
  return unifier( this, testdouble, op );
}

Grunden til alt denne problematik, er at jeg står med to GSL_Expression af ukendt type, og en binær operation. ( i et kald som : Binary_Expr(e1,e2,binop_sub); )
Ved hjælp af kald til den virtuelle funktion Unify på første operand kommer jeg til at kende typen på den. Vha RTTI kan jeg fange anden type - således at jeg kan oprette en instans af Unify, der om nødvendigt kan promovere (cast fra int til double) det ene argument, således at den binære operation kan laves på to argumenter af samme type.
Avatar billede stormy Nybegynder
06. december 2004 - 18:16 #4
Læs : således at jeg kan oprette en instans af Unification, der om ...
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
Kurser inden for grundlæggende programmering

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester