2012-08-25 10 views
5

Pregunta de fondo: boost.proto + detect invalid terminal before building the expression tree.boost.proto + modificar el árbol de expresiones en el lugar

Hola, lo que estoy tratando de lograr es

  1. crear una copia de un árbol de expresión, donde todos los vectores son sustituidos por sus comenzar iteradores (en mi caso, es el puntero en bruto)
  2. incremente los iteradores en el lugar
  3. iteradores de desreferencia en el árbol, pero esa parte debería ser relativamente fácil.

Así, por 1. terminé con este código

/////////////////////////////////////////////////////////////////////////////// 
// 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> > > 
     > 
{}; 

Lo anterior tiene éxito para crear un árbol donde todos los vectores se reemplazan por punteros. Hasta aquí todo bien. Ahora, intente incrementar los iteradores . Me di cuenta de que sería mejor avanzar iteradores, por lo que con una sola transformación, podría obtener la mayor parte del comportamiento de un iterador de acceso aleatorio (la otra parte faltante es la desreferencia). 2. Para, el requerido debe ser transformada

/////////////////////////////////////////////////////////////////////////////// 
// 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> > > 
     > 
{}; 

Ahora, en la función principal

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; 
} 

me sale el siguiente mensaje de error (filtrada a cabo la basura):

array.cpp: 361: 13: error: asignación de sólo lectura ubicación

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

lo que me molesta es la '((* & var)) 'parte ... no puede entender qué hacer para arreglar esto. Gracias de antemano, saludos cordiales

PS cosa no relacionada: después de jugar un poco con las transformaciones, el patrón general que estoy usando es:

  1. Decidir qué hacer con el árbol
  2. Escriba una transformación primitiva que realiza la operación
  3. Escriba una gramática que reconozca dónde se debe aplicar la transformación, utilice la transformación definida anteriormente

¿Crees que esto es razonable? Quiero decir, es una gran cantidad de código realizar solo una operación elemental a un solo tipo de nodo . Con contextos, es posible definir varias operaciones a la vez, discriminando en el tipo de nodo. ¿Es posible hacer esto con las transformaciones también? ¿Cuál es el patrón general que se utilizará?

+0

El mensaje de error significa que 'var' (donde intenta incrementarlo por' índice') es inmutable. ¿Has intentado utilizar un estilo más funcional, donde la transformación en cambio devuelve el siguiente iterador? –

+0

@LucDanton Intenté, si cambio el tipo de devolución en iter_advance y devuelvo un puntero modificado (he verificado que el puntero se incrementa en la transformación), el árbol no se cambia. Estaba siguiendo las 'increment_ints' en la gestión de proto, pero ahora me doy cuenta de que es diferente, en ese caso el árbol almacenaba referencias a int vars, ahora tengo los ptrs almacenados por valor en el árbol. Alternativas: 1. hacer una copia nueva de todo el árbol cada vez que incremente (enfoque puramente funcional?) B) almacenar los punteros en un iterator_wrapper como en el ejemplo "mixto" del manual. – Giuliano

Respuesta

4

Su intuición es correcta; deberías poder mutar el árbol en el lugar. Parece que hay algo de extraño const con la transformación de Proto pass_through que necesito investigar, por lo que la solución es un poco no obvia. Primero, defino algunos callables que usaré en los algoritmos de Proto. Prefiero callables a transformaciones primitivas porque son más simples de asimilar, más reutilizables y dan como resultado algoritmos de Proto más fáciles de leer.

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; 
    } 
}; 

Ahora, a resolver su problema de fragilidad con un simple adaptador de iterador:

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 << ")"; 
    } 
}; 

Aquí está el algoritmo para convertir un árbol que contiene vectores en un árbol que contiene iteradores vector.

// 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> >) 
     > 
    > 
{}; 

No se necesita la última proto::_byval. La transformada pass_through utilizada por proto::nary_expr no debe crear nodos temporales const. Lo siento por eso.

Y aquí está el algoritmo para avanzar todos los iteradores en el lugar. Cuando puedas asimilar esto completamente, serás verdaderamente un maestro 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 
      > 
     > 
    > 
{}; 

El truco para la comprensión de lo anterior es saber:

  1. proto::_void no hace nada y devuelve void
  2. proto::and_, cuando se utiliza como una transformación de este tipo, se ejecutan todas las transformaciones especificadas y devuelve el resultado de la última.

Después de todo eso, ahora puede hacer lo que se había propuesto hacer: A su vez un árbol que contiene vectores en un árbol que contiene los iteradores, y luego avanzar en todos los iteradores en el lugar:

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); 

Creo que tu código habría funcionado si no te hubieras topado con la rareza const. Además, creo que es más fácil hacerlo si escribe callables ordinarios en lugar de transformaciones primitivas.

Espero que esto ayude.

+0

Hola Eric, dame un poco de tiempo para verificar esta solución (y entender tu código) antes de aceptar tu respuesta (un poco ocupado 'morn') ... lo cual es seguro, ¡no hay dudas al respecto! Pero déjame darte las gracias ahora por tu amabilidad, hiciste mucho trabajo por mí. Por cierto, me parece proto adictivo ... :) (PD: estoy usando boost 1.50) – Giuliano

+2

FYI, la const rareza en la transformación 'pass_through' se soluciona en boost trunk. Debería ser parte de boost 1.52. Gracias por la respuesta; ¡Me alegra que te guste Proto! :) –

+0

Ok, tu código funciona para mí, gracias. Por cierto, también es un buen ejemplo del uso de otras bibliotecas de impulso (boost.range, boost.iterator ...) 'sobre la marcha' para 'impulsar' las tareas diarias. Dos preguntas: 1 ¿Por qué especializas el resultado y el resultado en callables? Veo que está relacionado con la constidad de E en el árbol, pero ¿cómo puedo predecir qué especializaciones se requieren en un caso dado (no por intento)? 2. La transformación final: hay un par de cosas que no entiendo, pero la más importante es: ¿por qué envuelves el vector_advance_algo más interno en la transformación and_?¡Gracias de nuevo! – Giuliano

Cuestiones relacionadas