2012-08-25 10 views
5

Domanda in background: boost.proto + detect invalid terminal before building the expression tree.boost.proto + modifica albero delle espressioni sul posto

Ciao, quello che sto cercando di realizzare è

  1. creare una copia di un albero di espressione, in cui tutti i vettori sono sostituiti con i loro cominciare iteratori (nel mio caso è un puntatore RAW)
  2. incrementare gli iteratori sul posto
  3. iteratori di deviazione nella struttura, ma quella parte dovrebbe essere relativamente semplice.

Così, per 1. Ho finito con questo codice

/////////////////////////////////////////////////////////////////////////////// 
// A transform that converts all vectors nodes in a tree to iterator nodes 
struct vector_begin : proto::transform <vector_begin> 
{ 
    template<typename Expr, typename Unused1, typename Unused2> 
    struct impl : boost::proto::transform_impl<Expr, Unused1, Unused2> 
    { 
     // must strip away the reference qualifier (&) 
     typedef typename proto::result_of::value< 
       typename boost::remove_reference<Expr>::type 
      >::type vector_type; 

     typedef typename proto::result_of::as_expr 
      <typename vector_type::const_iterator>::type result_type; 

     result_type operator()(
       typename impl::expr_param var 
      , typename impl::state_param 
      , typename impl::data_param) const 
     { 
      typename vector_type::const_iterator iter(proto::value(var).begin()); 
      return proto::as_expr(iter); // store iterator by value 
     } 
    }; 
}; 

struct vector_grammar_begin 
     : proto::or_ < 
      proto::when <vector_terminal, vector_begin> 
      // scalars want to be stored by value (proto stores them by const &), if not the code does not compile... 
      , proto::when <scalar_terminal, boost::proto::_make_terminal(boost::proto::_byval(boost::proto::_value))> 
      // descend the tree converting vectors to begin() iterators 
      , proto::when <proto::nary_expr<_, proto::vararg<vector_grammar_begin> > > 
     > 
{}; 

È possibile che questo riesce a creare un albero in cui tutti i vettori sono sostituiti da puntatori. Fin qui tutto bene. Ora, prova ad incrementare gli iteratori . Mi sono reso conto che sarebbe meglio far avanzare gli iteratori, quindi con una sola trasformazione, potrei ottenere la maggior parte del comportamento dello di un iteratore di accesso casuale (la dereferenziazione è l'altro pezzo mancante). Per 2., il richiesto dovrebbe essere trasformata

/////////////////////////////////////////////////////////////////////////////// 
// A transform that advances all iterators in a tree 
struct iter_advance : proto::transform <iter_advance> 
{ 
    template<typename Expr, typename Index, typename Dummy> 
    struct impl : boost::proto::transform_impl<Expr, Index, Dummy> 
    { 
     typedef void result_type; 
     result_type operator()(
       typename impl::expr_param var 
      , typename impl::state_param index // i'm using state to pass a data :(
      , typename impl::data_param) const 
     { 
      proto::value(var)+=index; // No good... compile error here :(
     } 
    }; 
}; 

// Ok, this is brittle, what if I decide the change vector<D,T>'s iterator type ? 
struct iter_terminal 
     : proto::and_< 
       proto::terminal<_> 
      , proto::if_<boost::is_pointer<proto::_value>()> 
      > 
{}; 


struct vector_grammar_advance 
     : proto::or_ < 
      proto::when <iter_terminal, iter_advance> 
      , proto::terminal<_> 
      , proto::when <proto::nary_expr<_, proto::vararg<vector_grammar_advance> > > 
     > 
{}; 

Ora, nella funzione principale

template <class Expr> 
void check_advance (Expr const &e) 
{ 
    proto::display_expr (e); 

    typedef typename boost::result_of<vector_grammar_begin(Expr)>::type iterator_type; 
    iterator_type iter = vector_grammar_begin()(e); 
    proto::display_expr (iter); 

    vector_grammar_advance()(iter,1); 
    proto::display_expr (iter); 
} 

int main (int, char**) 
{ 
    vec<3, double> a(1), b(2), c(3); 
    check_advance(2*a+b/c); 
    return 0; 
} 

ottengo il seguente messaggio di errore (filtrato la spazzatura):

array.cpp: 361: 13: errore: assegnazione di sola lettura posizione

'boost::proto::value<boost::proto::exprns_::expr<boost::proto::tagns_::tag::terminal, 
boost::proto::argsns_::term<const double*>, 0l> >((* & var))' 

Quello che mi preoccupa è il '((* & var)) "parte ... non riesco a capire cosa fare per risolvere questo problema. Grazie in anticipo, cordiali saluti

PS cosa non collegato: dopo aver giocato un po 'con trasforma, lo schema generale che sto utilizzando è:

  1. decidere cosa fare per l'albero
  2. Scrivi una trasformazione primitiva che esegue l'operazione
  3. Scrivi una grammatica che riconosca dove deve essere applicata la trasformazione, utilizza la trasformazione precedentemente definita

Pensi che sia ragionevole? Voglio dire, è un sacco di codice per eseguire solo un'operazione elementare su un singolo tipo di nodo . Con i contesti, è possibile definire più operazioni contemporaneamente, discriminando il tipo di nodo. E 'possibile farlo anche con le trasformazioni? Qual è lo schema generale da utilizzare?

+0

Il messaggio di errore significa che 'var' (dove si tenta di incrementarlo con' index') è immutabile. Hai provato a utilizzare uno stile più funzionale, in cui la trasformazione restituisce invece l'iteratore successivo? –

+0

@LucDanton Provato, se cambio il tipo di ritorno in iter_advance e restituisco un puntatore modificato (ho verificato che il puntatore è aumentato nella trasformazione), l'albero non viene modificato. Stavo seguendo le "increment_int" sul manuale di proto, ma ora mi rendo conto che è diverso, in quel caso l'albero stava memorizzando i riferimenti a v vars, ora ho i ptrs che sono memorizzati in base al valore nell'albero. Alternative: 1. fare una nuova copia dell'intero albero ogni volta che passo (approccio puramente funzionale?) B) memorizzare i puntatori in un iterator_wrapper come nell'esempio "misto" del manuale. – Giuliano

risposta

4

L'intuizione è corretta; dovresti essere in grado di mutare l'albero sul posto. Sembra che ci sia qualche strana stranezza con la trasformazione di Proto pass_through che ho bisogno di investigare, quindi la soluzione è un po 'non ovvia. Innanzitutto, definisco alcune callebles che userò negli algoritmi di Proto. Preferisco le callable alle trasformazioni primitive perché sono più semplici da eliminare, più riutilizzabili e risultano in algoritmi Proto più facili da leggere.

struct begin 
    : proto::callable 
{ 
    template<typename Sig> 
    struct result; 

    template<typename This, typename Rng> 
    struct result<This(Rng)> 
     : boost::range_iterator<Rng> 
    {}; 

    template<typename This, typename Rng> 
    struct result<This(Rng &)> 
     : boost::range_iterator<Rng> 
    {}; 

    template<typename Rng> 
    typename boost::range_iterator<Rng>::type 
    operator()(Rng &rng) const 
    { 
     return boost::begin(rng); 
    } 

    template<typename Rng> 
    typename boost::range_iterator<Rng const>::type 
    operator()(Rng const &rng) const 
    { 
     return boost::begin(rng); 
    } 
}; 

struct advance 
    : proto::callable 
{ 
    typedef void result_type; 

    template<typename Iter> 
    void operator()(Iter &it, unsigned d) const 
    { 
     it += d; 
    } 
}; 

Ora, io risolvere il problema fragilità con un semplice adattatore iteratore:

template<typename Iter> 
struct vector_iterator 
    : boost::iterator_adaptor<vector_iterator<Iter>, Iter> 
{ 
    vector_iterator() 
     : boost::iterator_adaptor<vector_iterator<Iter>, Iter>() 
    {} 

    explicit vector_iterator(Iter iter) 
     : boost::iterator_adaptor<vector_iterator<Iter>, Iter>(iter) 
    {} 

    friend std::ostream &operator<<(std::ostream &sout, vector_iterator it) 
    { 
     return sout << "vector_iterator(value: " << *it << ")"; 
    } 
}; 

Ecco l'algoritmo per trasformare un albero contenente i vettori in un albero contenente iteratori vettoriali.

// Turn all vector terminals into vector iterator terminals 
struct vector_begin_algo 
    : proto::or_< 
     proto::when< 
      proto::terminal<std::vector<_, _> > 
      , proto::_make_terminal(
       vector_iterator<begin(proto::_value)>(begin(proto::_value)) 
      ) 
     > 
     , proto::when< 
      proto::terminal<_> 
      , proto::_make_terminal(proto::_byval(proto::_value)) 
     > 
     , proto::otherwise< 
      proto::_byval(proto::nary_expr<_, proto::vararg<vector_begin_algo> >) 
     > 
    > 
{}; 

non dovrebbe essere necessario l'ultimo proto::_byval. La trasformazione pass_through utilizzata da proto::nary_expr non deve creare nodi temporanei const. Mi dispiace per quello

Ed ecco l'algoritmo per far avanzare tutti gli iteratori sul posto. Quando riuscirai a farlo completamente, sarai davvero un maestro di Proto.

// Mutate in-place by advancing all vector iterators the amount 
// in the state parameter 
struct vector_advance_algo 
    : proto::or_< 
     proto::when< 
      proto::terminal<vector_iterator<_> > 
      , advance(proto::_value, proto::_state) 
     > 
     , proto::when< 
      proto::terminal<_> 
      , proto::_void 
     > 
     , proto::otherwise< 
      proto::and_< 
       proto::fold< 
        _ 
        , proto::_state 
        , proto::and_< 
         vector_advance_algo 
         , proto::_state 
        > 
       > 
       , proto::_void 
      > 
     > 
    > 
{}; 

Il trucco per comprendere quanto sopra è sapere:

  1. proto::_void non fa nulla e restituisce void
  2. proto::and_, se usato come una trasformazione come questo, esegue tutte le trasformazioni specificate e restituisce il risultato dell'ultimo

Dopo tutto questo, ora è possibile fare ciò che si era proposto di fare: girare un albero contenente i vettori in un albero contenente iteratori, e quindi far avanzare tutti gli iteratori sul posto:

proto::literal<std::vector<int> > vec1; 
proto::value(vec1).assign(
    boost::make_counting_iterator(0) 
    , boost::make_counting_iterator(16) 
); 

auto beg = vector_begin_algo()(2 * vec1 + vec1); 
proto::display_expr(beg); 

vector_advance_algo()(beg, 1u); 
proto::display_expr(beg); 

vector_advance_algo()(beg, 1u); 
proto::display_expr(beg); 

Penso che il tuo codice avrebbe funzionato se non fossi imbattuto nella strana stranezza. Inoltre, penso che potresti avere un momento più semplice se scrivi delle callables ordinarie invece delle trasformazioni primitive.

Spero che questo aiuti.

+0

Ehi, Eric, dammi un po 'di tempo per controllare questa soluzione (e capisco il tuo codice) prima di accettare la tua risposta (un po' affollato di mattina) ... il che è corretto, certo, non ci sono dubbi! Ma ti prego, grazie per la tua gentilezza, hai fatto un sacco di lavoro per me. BTW trovo proto-avvincente ... :) (PS: sto usando boost 1.50) – Giuliano

+2

FYI, la stranezza const nella trasformazione 'pass_through' è fissata nel bagagliaio boost. Dovrebbe essere parte della spinta 1.52. Grazie per il feedback; Sono contento che ti piaccia il proto! :) –

+0

Ok, il codice funziona per me, grazie. A proposito, è anche un bell'esempio di utilizzo di altre librerie boost (boost.range, boost.iterator ...) 'al volo' per 'potenziare' le attività quotidiane. Due domande: 1 Perché specializzi entrambi i risultati e ottieni in callables. Vedo che è legato alla costanza di E nell'albero, ma come posso prevedere quali specializzazioni sono richieste in un determinato caso (no per tentativo)? 2. La trasformazione finale: ci sono un paio di cose che non capisco, ma il più importante è: perché avvolgi il più profondo vettore_advance_algo nella trasformazione and_?Grazie ancora! – Giuliano