2012-06-22 5 views
6

Mam kilka klas, które z różnych powodów są poza zakresem tej dyskusji, nie można zmodyfikować (szczegóły nieistotne wdrożeniowe pominięta):Adaptacja nie iterable pojemników należy powtórzyć za zwyczaj templatized iterator

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

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

(Nie jest wiele podobnych klas "Foo" i "Bar", z którymi mam do czynienia, i to wszystko jest generowany kod z innego miejsca i rzeczy, których nie chcę podklasy itp.)

[Edytuj: wyjaśnienie - choć jest wiele podobne klasy "Foo" i "Bar" gwarantują, że każda klasa "zewnętrzna" będzie miała metody gettera i wielkości. Tylko nazwa metody getter i typ powrotu będą się różnić dla każdego "zewnętrznego", w zależności od tego, jaki jest "wewnętrzny" typ zawarty w nim.

Więc jeśli mam Baz który zawiera instancje quux, nie będzie quux & Baz :: get_quux (size_t indeksu), a size_t Baz :: size_quux().]

Ze względu na konstrukcję klasy Bar , nie możesz z łatwością używać go w algorytmach STL (np. for_each, find_if, itp.), i musi wykonywać pętle bezwzględne, zamiast przyjmować podejście funkcjonalne (powody, dla których wolę to ostatnie jest również poza zakresem tej dyskusji):

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' ... */ 
} 

Więc ... Nigdy nie stworzyłem niestandardowego iteratora i po przeczytaniu różnych pytań/odpowiedzi na SO o iterator_traits i tym podobne, wpadłem na to (obecnie na wpół wypalone) "rozwiązanie":

Po pierwsze, niestandardowy mechanizm iteratora (UWAGA: wszystkie zastosowania "funkcji" i "bindowania" pochodzą ze std :: tr1 w 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; 
}; 

Następnie mechanizm, dzięki któremu mogę ważne iteratory reprezentujący początek i koniec pojemnika wewnętrznego:

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

mogę korzystać z tych klas w następujący sposób:

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

Empirycznie to rozwiązanie działa poprawnie (pomijając wszelkie kopie/wklejanie lub literówki, które mogłem wprowadzić podczas edycji powyższego dla zwięzłości).

Więc wreszcie rzeczywiste pytanie:

nie lubię wymagające użycia bind() i _1 Symbole zastępcze, etcetera przez dzwoniącego. Wszystko, na czym im zależy, to: typ zewnętrzny, typ wewnętrzny, metoda typu zewnętrznego do pobierania instancji wewnętrznych, metoda typu zewnętrznego do pobierania zliczeń wewnętrznych.

Czy istnieje sposób, aby jakoś "ukryć" wiązanie w ciele klas szablonów? Nie mogłem znaleźć sposobu na osobne dostarczanie parametrów szablonu dla typów i metod wewnętrznych oddzielnie ...

Dzięki!
David

+0

Jeśli wszystko, czego szukasz, to unikanie 'std :: bind' i' std :: placeholders', możesz nadużywać faktu, że np. 'std :: function ' jest całkowicie zadowolony z połknięcia funkcji typu wskaźnik-do-członka 'Foo & (Bar :: *) (size_t)' – Managu

Odpowiedz

2

przemian, zawsze można wziąć funkcje jako parametry się szablon, jeśli mają one przewidywalne Podpis:

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(), /*...*/); 
} 

Albo pójść nawet dalej (http://ideone.com/ulIC7) i przekazać funkcji owijając je w 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

Myślę, że ta odpowiedź najbardziej bezpośrednio odpowiada na moje pytanie, biorąc pod uwagę, że wewnętrznie ukrywa ona funkcję <>, którą wykonywałem zewnętrznie za pomocą wywołań <>, więc wybiorę ją jako "odpowiedź". Myślę jednak, że bardziej podoba mi się twoje podejście do LegacyContainerTraits i prawdopodobnie wykorzystam to podejście. Bardzo doceniane! – DAldridge

3

Można zdefiniować strukturę szablonu pomocnika, aby ukryć rzeczywistą mechanikę interakcji z Foo i Bar.A następnie specjalizować się za kontener:

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

Następnie w ContainerProxy/ContainerIterator, zamiast przechowywania i korzystania z funkcji, wystarczy przechowywać odniesienie/skopiować do pojemnika i wywołać odpowiednią specjalizację LegacyContainerTraits. Rzeczywiście, nie ma naprawdę żadnej potrzeby ContainerProxy w ogóle:

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

Następnie można dość łatwo korzystać z darmowych funkcji w pętli lub algorytm. Nawet w zakres oparte na pętli:

Bar b=...; 

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

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

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

Więcej uregulowana wersji: http://ideone.com/JA9hC

+0

Jak rozszerzyć to rozwiązanie do obsługi odwołania do stałych jako dane wejściowe? (tj. implementacje begin <> i end <>, które pobierają (const TOuter i)). Oddzielna klasa ConstLegacyIteratorTraits <>, ponieważ ostatecznie sprowadza się ona do posiadania "const TOuter & mContainerRef;"? – DAldridge

+0

Myślę, że dodałem specjalizację 'LegacyIteratorTraits ', definiującą 'inner_type' jako' const Foo'. Wygląda na to, że wszystko inne działa: http://ideone.com/8iVVg – Managu

+0

Lub nawet częściowa specjalizacja dla 'LegacyIteratorTraits ' która beztrosko 'const_cast's usuwa problem: http://ideone.com/01gBW – Managu

2

Oto pełna implementacja wariantu rozwiązania Managu. (Cóż, wiem, że nie zaimplementowałem wszystkich wymaganych funkcji iteratora wymaganych do uzyskania rzeczywistego iteratora dostępu losowego. Implementacja reszty z nich jest ćwiczeniem dla czytelnika.).

#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

Uczynienie z Iteratora klasy zagnieżdżonej jest miłym akcentem. Dlaczego o tym nie pomyślałem? – Managu

Powiązane problemy