2010-12-13 12 views
8

Biorąc pod uwagę następujące elementy:Czy mogę napisać funktor C++, który akceptuje zarówno surowy wskaźnik, jak i inteligentny wskaźnik?

struct Foo 
{ 
    int bar() const; 
}; 

struct IsEqual : public std::unary_function<Foo*, bool> 
{ 
    int val; 
    IsEqual(int v) : val(v) {} 

    bool operator()(const Foo* elem) const 
    { 
     return elem->bar() == val; 
    } 
}; 

mam pojemnik Foo* i używam std::find_if i std::not1, aby dowiedzieć się, czy są jakieś elementy w pojemniku, w którym bar() powraca czymś różni się od podanej wartości. Kod wygląda następująco:

// Are all elements equal to '2'? 
bool isAllEqual(const std::vector<Foo*> &vec) 
{ 
    return find_if(vec.begin(), vec.end(), std::not1(IsEqual(2))) == vec.end(); 
} 

Przewijanie do przodu w przyszłość i teraz mam inny pojemnik, tym razem zawierająca std::tr1::shared_ptr<Foo>. Chciałbym po prostu ponownie użyć mojego funktora w przeładowanej wersji isAllEqual(). Ale nie mogę. Foo* i shared_ptr<Foo> to różne typy. I muszę odziedziczyć po unary_function, więc mogę użyć not1. Byłoby bardziej elegancko, gdybym dwukrotnie unikał pisania tego samego funktora.

Pytania:

  • Czy istnieje jakiś sposób, aby napisać IsEqual więc można go używać zarówno surowych i inteligentne kursory?
  • Czy założyłem sobie kajdanki przy użyciu std::not1? Czy powinienem zamiast tego napisać IsNotEqual?

Ograniczenia:

  1. Nie mogę używać niczego z biblioteki boost.
  2. Nasz kompilator nie jest wystarczająco fajny, aby obsługiwać lambdy w C++ 0x.
+1

To brzmi jak przykład, gdzie szablony byłyby miłe. – GWW

+0

@Kristo: Czy Twój kompilator jest wystarczająco fajny, aby zapewnić inne rzeczy w C++ 0x, takie jak 'std :: begin'? –

+0

@Ben, używamy gcc 4.1.2, więc prawdopodobnie nie. 'std :: begin' i' std :: end' powinny być trywialne, aby pisać. –

Odpowiedz

2
// --*-- C++ --*-- 

#include <vector> 
#include <algorithm> 
#include <iostream> 

// Template unary function example. 
template <typename T> 
struct IsEqual : public std::unary_function<T, bool> 
{ 
    int v; 

    IsEqual (int v) : v (v) {} 

    bool operator() (const T & elem) const 
    { 
     return elem ? elem->bar() == v : false; 
    } 
}; 

// Generic algorithm implementation example... 
template <typename T1, typename T2> 
bool isAllEqual (const T1 & c, T2 v) 
{ 
    return find_if (
     c.begin(), c.end(), 
     std::not1 (IsEqual <typename T1::value_type> (v))) == c.end(); 
} 

// Some arbitrary pointer wrapper implementation, 
// provided just for an example, not to include any 
// specific smart pointer implementation. 
template <typename T> 
class WrappedPtr 
{ 
    const T *v; 

public: 
    typedef void (WrappedPtr<T>::*unspecified_boolean_type)() const; 

    WrappedPtr (const T *v) : v (v) {} 

    const T *operator ->() const { return v; } 

    operator unspecified_boolean_type() const 
    { 
     return v != NULL ? 
      &WrappedPtr<T>::unspecified_boolean_true : NULL; 
    } 

private: 
    void unspecified_boolean_true() const {} 
}; 

// Example of structure that could be used with our algorithm. 
struct Foo 
{ 
    int v; 

    Foo (int v) : v (v) {} 

    int bar() const 
    { 
     return v; 
    } 
}; 

// Usage examples... 
int main() 
{ 
    Foo f1 (2), f2 (2); 

    // Example of using raw pointers... 
    { 
     std::vector<Foo *> vec; 
     vec.push_back (NULL); 
     vec.push_back (&f1); 
     vec.push_back (&f2); 

     if (isAllEqual (vec, 2)) 
      std::cout << "All equal to 2" << std::endl; 
     else 
      std::cout << "Not all equal to 2" << std::endl; 
    } 

    // Example of using smart pointers... 
    { 
     std::vector< WrappedPtr<Foo> > vec; 
     vec.push_back (NULL); 
     vec.push_back (&f1); 
     vec.push_back (&f2); 

     if (isAllEqual (vec, 2)) 
      std::cout << "All equal to 2" << std::endl; 
     else 
      std::cout << "Not all equal to 2" << std::endl; 
    } 
} 
+0

+1 dla sprawdzania zerowego wskaźnika w 'operator()'. Dla mnie wygląda dobrze. –

+0

@Vlad: Nie działa ze starymi zwykłymi tablicami. Czy jest też dobry pomysł, aby 'unary_function :: Arg' był inny od typu parametru' operator()() '? –

+0

@Ben: Myślę, że dla stałego odniesienia do tego samego typu jest OK. Aby obsługiwać tablice, domyślam się, że dla prostych tablic musisz mieć inną specjalizację, taką jak szablon void foo (const (& array) [Len]) ... lub coś w tym stylu. –

2

Mój strzał byłoby coś takiego:

template<typename PtrToFoo> 
struct IsEqual : public std::unary_function<PtrToFoo, bool> 
{ 
    int val; 
    IsEqual(int v) : val(v) {} 

    bool operator()(PtrToFoo elem) const 
    { 
     return elem->bar() == val; 
    } 
}; 

Będziesz mieć inny operator() instancji za wszystko dereferencable z ->, tak surowe wskaźniki i inteligentne wskaźniki.

+0

Umm, o tej podstawowej klasie ... –

+0

Możesz to zrobić? Sądziłem, że pierwszy argument szablonu "unary_function" musi być zgodny z typem argumentu 'operator()'. –

+0

Nie, nie możesz. Tak. –

8

Jak o:

template<typename T> 
struct IsEqual : public std::unary_function<const T&, bool> 
{ 
    int val; 
    IsEqual(int v) : val(v) {} 

    bool operator()(const T& elem) const 
    { 
     return elem->bar() == val; 
    } 
}; 

template<typename T> 
IsEqual<T> DeduceEqualityComparer(int v, T) { return IsEqual<T>(v); } 

// Are all elements equal to '2'? 
template<typename TContainer> 
bool isAllEqual(const TContainer& coll) 
{ 
    using std::begin; // in C++0x, or else write this really simple function yourself 
    using std::end; 
    if (begin(coll) == end(coll)) return true; 
    return find_if(begin(coll), end(coll), std::not1(DeduceEqualityComparer(2, *begin(coll)))) == end(coll); 
} 
+0

Hmmm, podoba mi się. Jednak tak naprawdę nie jest to mniej typowanie niż 'IsEqual >' (a wtedy nie musiałbym pisać odliczającego). +1 mimo wszystko. –

+0

@Kristo: Ok, ale teraz utwórz 'isAllEqual' również szablon, nadchodzi kod. –

+0

Można również podać 'IsEqual' jako domyślny argument szablonu:' szablon struct IsEqual ... 'A następnie nadal używać' IsEqual' jak poprzednio dla iteratorów na 'Foo *'. – aschepler

1

Można być może coś zrobić trudne z ukrytych konwersji:

class IsEqualArg { 
public: 
    // Implicit conversion constructors! 
    IsEqualArg(Foo* foo) : ptr(foo) {} 
    IsEqualArg(const std::tr1::shared_ptr<Foo>& foo) : ptr(&*foo) {} 
private: 
    Foo* ptr; 
    friend struct IsEqual; 
}; 

struct IsEqualArg : public std::unary_function<IsEqualArg, bool> { 
    bool operator()(const IsEqualArg& arg) const; 
    //... 
}; 

Ale tak naprawdę raczej tylko napisać IsNotEqual.

0

Odpowiedź Bena jest naprawdę jedyną rzeczą, jaką możesz zrobić w C++ 03. W C++ 0x jednak i/lub z boost :: bind, nie musisz dziedziczyć z unary_function. Pozwala to na użycie operatora templated(). Zwykle można uciec z tym samym w C++ 03, ale myślę, że jest to technicznie niepoprawne, aby to zrobić.

+0

TR1 'bind' może być w porządku. Musiałbym sprawdzić ludzi, którzy pracują z naszym kodem. I nie jest niepoprawne pisanie szablonowego 'operator()'. Używanie 'not1' jest punktem zwrotnym. –

Powiązane problemy