2012-06-22 9 views
6

tengo algunas clases, que por diversas razones fuera del alcance de esta discusión, no puede modificar (los detalles de implementación irrelevantes omitidas):Adaptación de envases no iterables que se itera a través de encargo a plantillas iterador

class Foo { /* ... irrelevant public interface ... */ }; 

class Bar { 
    public: 
    Foo& get_foo(size_t index) { /* whatever */ } 
    size_t size_foo() { /* whatever */ } 
}; 

(Hay hay muchas clases similares 'Foo' y 'Bar' con las que estoy tratando, y todo es código generado de otra parte y cosas que no quiero subclasificar, etc.)

[Editar: aclaración - aunque hay muchos clases similares 'Foo' y 'Bar', se garantiza que cada clase "externa" tendrá los métodos getter y size. Solo el nombre del método getter y el tipo de retorno diferirán para cada "exterior", según el tipo que contenga "interno".

Por lo tanto, si tengo que contiene Baz casos quux, habrá quux & Baz :: get_quux (size_t índice), y size_t Baz :: size_quux().]

dado el diseño de la clase Bar , no puede usarlo fácilmente en algoritmos AWL (por ejemplo, for_each, find_if, etc.) y debe realizar bucles imperativos en lugar de adoptar un enfoque funcional (razones por las que prefiero que el último también esté fuera del alcance de esta discusión):

Bar b; 
size_t numFoo = b.size_foo(); 
for (int fooIdx = 0; fooIdx < numFoo; ++fooIdx) { 
    Foo& f = b.get_foo(fooIdx); 
    /* ... do stuff with 'f' ... */ 
} 

Así que ... Nunca he creado un iterador personalizado, y después de leer varias preguntas/respuestas en SO sobre iterator_traits y similares, se me ocurrió esta "solución" (actualmente medio cocida):

Primero, el mecanismo iterador personalizado (NOTA: todos los usos de 'función' y 'vincular' son de std :: tr1 en MSVC9):

// Iterator mechanism... 
template <typename TOuter, typename TInner> 
class ContainerIterator : public std::iterator<std::input_iterator_tag, TInner> { 
    public: 
    typedef function<TInner& (size_t)> func_type; 

    ContainerIterator(const ContainerIterator& other) : mFunc(other.mFunc), mIndex(other.mIndex) {} 

    ContainerIterator& operator++() { ++mIndex; return *this; } 

    bool operator==(const ContainerIterator& other) { 
     return ((mFunc.target<TOuter>() == other.mFunc.target<TOuter>()) && (mIndex == other.mIndex)); 
    } 

    bool operator!=(const ContainerIterator& other) { return !(*this == other); } 

    TInner& operator*() { return mFunc(mIndex); } 

    private: 
    template<typename TOuter, typename TInner> 
    friend class ContainerProxy; 

    ContainerIterator(func_type func, size_t index = 0) : mFunc(func), mIndex(index) {} 

    function<TInner& (size_t)> mFunc; 
    size_t mIndex; 
}; 

a continuación, el mecanismo por el cual consigo iteradores válidas que representan comenzar y al final del contenedor interior:

// Proxy(?) to the outer class instance, providing a way to get begin() and end() 
// iterators to the inner contained instances... 
template <typename TOuter, typename TInner> 
class ContainerProxy { 
    public: 
    typedef function<TInner& (size_t)> access_func_type; 
    typedef function<size_t()> size_func_type; 

    typedef ContainerIterator<TOuter, TInner> iter_type; 

    ContainerProxy(access_func_type accessFunc, size_func_type sizeFunc) : mAccessFunc(accessFunc), mSizeFunc(sizeFunc) {} 

    iter_type begin() const { 
     size_t numItems = mSizeFunc(); 
     if (0 == numItems) return end(); 
     else return ContainerIterator<TOuter, TInner>(mAccessFunc, 0); 
    } 
    iter_type end() const { 
     size_t numItems = mSizeFunc(); 
     return ContainerIterator<TOuter, TInner>(mAccessFunc, numItems); 
    } 

    private: 
    access_func_type mAccessFunc; 
    size_func_type mSizeFunc; 
}; 

puedo utilizar estas clases de la siguiente manera:

// Sample function object for taking action on an LMX inner class instance yielded 
// by iteration... 
template <typename TInner> 
class SomeTInnerFunctor { 
    public: 
    void operator()(const TInner& inner) { 
     /* ... whatever ... */ 
    } 
}; 

// Example of iterating over an outer class instance's inner container... 
Bar b; /* assume populated which contained items ... */ 
ContainerProxy<Bar, Foo> bProxy(
    bind(&Bar::get_foo, b, _1), 
    bind(&Bar::size_foo, b)); 
for_each(bProxy.begin(), bProxy.end(), SomeTInnerFunctor<Foo>()); 

Empíricamente, esta solución funciona correctamente (menos cualquier copia/pega o errores tipográficos que pueda haber introducido al editar lo anterior para abreviar).

Así que, finalmente, la pregunta real:

no me gusta que requiere el uso de bind() y _1 marcadores de posición, etcétera por la persona que llama. Todo lo que realmente les importa es: tipo externo, tipo interno, método del tipo externo para captar instancias internas, método del tipo externo para captar instancias internas.

¿Hay alguna manera de "ocultar" el enlace en el cuerpo de las clases de plantilla de alguna manera? No he podido encontrar una manera de suministrar por separado los parámetros de plantilla para los tipos y métodos internos por separado ...

¡Gracias!
David

+0

Si todo lo que busca es evitar 'std :: bind' y' std :: placeholders', puede abusar del hecho de que, p. 'std :: function ' es perfectamente feliz de tragar el tipo de función puntero-a-miembro 'Foo & (Bar :: *) (size_t)' – Managu

Respuesta

2

Como alternativa, siempre se puede tomar las funciones como plantilla parámetros de sí mismos, si tienen una firma predecible:

template <typename TOuter, typename TInner, 
      TInner& (TOuter::*getfunc)(size_t)> 
class ContainerIterator 
{ 
public: 
    //... 
    TInner& operator*() {return mContainerRef.*getfunc(mIndex);} 
    //... 
}; 

template <typename TOuter, typename TInner, 
      size_t (TOuter::*sizefunc)(), 
      TInner& (TOuter::*getfunc)(size_t)> 
class ContainerProxy 
{ 
public: 
    //... 
    ContainerIterator<TOuter, TInner, getfunc> end() { 
     return ContainerIterator<TOuter, TInner, getfunc> 
        (mContainerRef, 
        mContainerRef.*sizefunc()); 
    } 
    //... 
}; 

int main() 
{ 
    Bar b; 
    ContainerProxy<Bar, Foo, &Bar::size_foo, &Bar::get_foo> proxy(b); 
    std::for_each(proxy.begin(), proxy.end(), /*...*/); 
} 

O ir aún más lejos (http://ideone.com/ulIC7) y pasar las funciones envolviéndolos en std::function:

template <typename TOuter, typename TInner> 
struct ContainerIterator : public std::iterator<std::input_iterator_tag, TInner> 
{ 
    TOuter& mContainerRef; 
    //... 
    typedef std::function<TInner& (TOuter*, size_t)> getfunc_type; 
    getfunc_type mGetfunc; 

    ContainerIterator(TOuter& containerRef, size_t index, getfunc_type const& getFunc) 
    /* ... */ {} 
    TInner& operator*() {return mGetfunc(&mContainerRef, mIndex);} 
    ContainerIterator<TOuter, TInner>& operator++() {++mIndex; return *this;} 

    // ... 
}; 

template <typename TOuter, typename TInner> 
struct ContainerProxy 
{ 
    TOuter& mContainerRef; 

    typedef std::function<size_t (TOuter*)> sizefunc_type; 
    sizefunc_type mSizefunc; 

    typedef std::function<TInner& (TOuter*, size_t)> getfunc_type; 
    getfunc_type mGetfunc; 

    ContainerProxy(TOuter& containerRef, sizefunc_type sizefunc, getfunc_type getfunc) 
    // ... 
    { 
    } 

    // ... 

    ContainerIterator<TOuter, TInner> end() const 
    { 
     return ContainerIterator<TOuter, TInner>(mContainerRef, 
               mSizefunc(&mContainerRef), 
               mGetfunc); 
    } 
}; 

int main() 
{ 
    Bar b=...; 

    ContainerProxy<Bar, Foo> proxy(b, &Bar::size_foo, &Bar::get_foo); 
    std::for_each(proxy.begin(), proxy.end(), /*...*/); 
} 
+0

Creo que esta respuesta responde más directamente a mi pregunta, dado que oculta internamente la función <> construcción que estaba haciendo externamente con las llamadas bind <>, por lo que la escogeré como la "respuesta". Sin embargo, creo que me gusta más su enfoque LegacyContainerTraits, y probablemente usaré ese enfoque. ¡Muy apreciado! – DAldridge

3

Puede definir una estructura de plantilla auxiliar para ocultar la mecánica real de interactuar con Foo y Bar.Y luego se especializan por contenedor:

// incomplete general case 
template <typename TOuter> struct LegacyContainerTraits; 

// Specialization for 'Bar' 
template <> struct LegacyContainerTraits<Bar> 
{ 
    // The inner type of 'Bar' is 'Foo' 
    typedef Foo inner_type; 

    static size_t get_size(Bar const& outer) {return outer.size_foo();} 
    static Foo& get_element(Bar const& outer, size_t index) { 
     return outer.get_foo(index); 
    } 
}; 

// Specialization for Baz 
template <> struct LegacyContainerTraits<Baz> 
{ 
    // The inner type of 'Baz' is 'Quux' 
    typedef Quux inner_type; 

    static size_t get_size(Baz const& outer) {return outer.size_quux();} 
    static Quux& get_element(Baz const& outer, size_t index) { 
     return outer.get_quux(index); 
    } 
}; 

Luego, en ContainerProxy/ContainerIterator, en lugar de almacenar y utilizar las funciones, que acaba de almacenar una referencia/copiar al recipiente y se invoca la especialización adecuada de LegacyContainerTraits. De hecho, no hay realmente ninguna necesidad de ContainerProxy en absoluto:

template <typename TOuter> class LegacyContainerIterator; 
template <typename TOuter> LegacyContainerIterator<TOuter> begin(TOuter&); 
template <typename TOuter> LegacyContainerIterator<TOuter> end(TOuter&); 

template <typename TOuter> 
class LegacyContainerIterator : 
    public std::iterator<std::random_access_iterator_tag, 
         typename LegacyContainerTraits<TOuter>::inner_type > 
{ 
private: 
    ... 
    friend LegacyContainerIterator<TOuter> begin<TOuter>(TOuter&); 
    friend LegacyContainerIterator<TOuter> end<TOuter>(TOuter&); 
    LegacyContainerIterator(TOuter& containerRef, size_t index) ... {}; 
    ... 

public: 
    ... 
    typename LegacyContainerTraits<TOuter>::inner_type& operator*() { 
     return LegacyContainerTraits<TOuter> 
      ::get_element(mContainerRef, mIndex); 
    } 
    ... 
}; 

template <typename TOuter> 
LegacyContainerIterator<TOuter> begin(TOuter& containerRef) 
{ 
    return LegacyContainerIterator<TOuter>(containerRef, 0); 
} 

template <typename TOuter> 
LegacyContainerIterator<TOuter> end(TOuter& containerRef) 
{ 
    return LegacyContainerIterator<TOuter>(
       containerRef, 
       LegacyContainerTraits<TOuter>::get_size(containerRef)); 
} 

continuación, puede utilizar las funciones gratuitas con bastante facilidad en un bucle o algoritmo. Incluso en una basada en la gama de bucle:

Bar b=...; 

for (auto it=begin(b); it!=end(b); ++it) {...} 

for (auto f : b) {...} 

std::for_each(begin(b), end(b), ...); 

Más concretarse versión: http://ideone.com/JA9hC

+0

¿Cómo extendería esta solución para manejar const referencias como entrada? (es decir, implementaciones begin <> y end <> que toman (const TOuter &)). Separa la clase ConstLegacyIteratorTraits <>, ya que en última instancia se reduce a tener un 'const TOuter & mContainerRef;'? – DAldridge

+0

Creo que agregaría una especialización 'LegacyIteratorTraits ', que define 'inner_type' para ser' const Foo'. Parece que todo lo demás funciona: http://ideone.com/8iVVg – Managu

+0

O incluso una especialización parcial para 'LegacyIteratorTraits ' que alegremente 'const_cast's aleja el problema: http://ideone.com/01gBW – Managu

2

Aquí hay una implementación completa de una variante de la solución de Managu. (Bueno, sé que no he implementado todas las funciones de iterador requeridas para obtener un iterador de acceso aleatorio real ... la implementación del resto de ellas es un ejercicio para el lector).

#include <vector> 
#include <iostream> 
#include <iterator> 

class Foo { 
    public: 
    Foo(int ii) : i(ii) {} 
    Foo() : i() {} 
    int i; 
}; 

class Bar { 
    public: 
    Bar() : f1(1), f2(2), f3(3) {} 

    Foo& get_foo(size_t i) { 
     if(i==0) return f1; 
     if(i==1) return f2; 
     return f3; 
    } 

    size_t n_foo() const { return 3; } 

    Foo f1; 
    Foo f2; 
    Foo f3; 
}; 


template< 
    typename INNER, 
    typename OUTER, 
    size_t(OUTER::*COUNTER)() const, 
    INNER&(OUTER::*ACCESSOR)(size_t) > 
class ContainerProxy { 
    public: 
    ContainerProxy(OUTER * o) : outer(o) {} 

    OUTER * outer; 
    struct Iterator { 
     typedef std::random_access_iterator_tag iterator_category; 
     typedef INNER      value_type; 
     typedef ptrdiff_t     difference_type; 
     typedef INNER*      pointer; 
     typedef INNER&      reference; 

     ContainerProxy * container; 
     size_t index; 

     Iterator(ContainerProxy * c, size_t i) : container(c), index(i) {} 
     Iterator& operator++() { index++; return *this; } 
     INNER& operator*() { return (container->outer->*ACCESSOR)(index); } 

     difference_type operator-(const Iterator other) const { return index-other.index; } 
    }; 

    Iterator begin() { return Iterator(this,0); } 

    Iterator end() { return Iterator(this, (outer->*COUNTER)()); } 
}; 



int main() { 
    Bar bar; 
    ContainerProxy<Foo,Bar, &Bar::n_foo, &Bar::get_foo> container(&bar); 

    std::vector<Foo> v(3); 
    std::copy(container.begin(), container.end(), v.begin()); 

    std::cout<<v[0].i<<std::endl; 
    std::cout<<v[1].i<<std::endl; 
    std::cout<<v[2].i<<std::endl; 
} 
+0

Hacer que el iterador sea una clase anidada es un buen toque. ¿Por qué no pensé en eso? – Managu

Cuestiones relacionadas