2010-12-13 2 views
8

Учитывая следующее:Могу ли я написать функтор C++, который принимает как необработанный указатель, так и умный указатель?

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

У меня есть контейнер Foo* и я использую std::find_if и std::not1, чтобы выяснить, есть ли какие-либо элементы в контейнере, где bar() возвращается нечто отличное от заданного значения. Код выглядит следующим образом:

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

Перемотка вперед в будущее, и теперь у меня есть другой контейнер, содержащий этот раз std::tr1::shared_ptr<Foo>. Я бы хотел просто повторно использовать мой функтор в перегруженной версии isAllEqual(). Но я не могу. Foo* и shared_ptr<Foo> - это разные типы. И мне нужно наследовать от unary_function, поэтому я могу использовать not1. Было бы более элегантно, если бы я мог дважды написать тот же самый функтор.

Вопросы:

  • Есть ли способ, чтобы написать IsEqual так что он может использовать как сырье и смарт-указатели?
  • Я сам надел наручники, используя std::not1? Должен ли я просто написать IsNotEqual?

Ограничения:

  1. Я ничего не могу из библиотеки повышающего использовать.
  2. Наш компилятор не достаточно крут, чтобы поддерживать C++ 0x lambdas.
+1

Это звучит как пример, где шаблоны были бы хороши. – GWW

+0

@Kristo: Является ли ваш компилятор достаточно крутым, чтобы предоставить другой материал C++ 0x, например 'std :: begin'? –

+0

@Ben, мы используем gcc 4.1.2, поэтому, вероятно, нет. «std :: begin» и 'std :: end' должны быть тривиальны для записи. –

ответ

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 для проверки нулевого указателя в 'operator()'. Выглядит хорошо. –

+0

@ Vlad: Не работает с хорошими старыми обычными массивами. Кроме того, хорошая идея для 'unary_function :: Arg' отличаться от типа параметра' operator()() '? –

+0

@Ben: Я думаю, что для постоянной ссылки на тот же тип это нормально. Для поддержки массивов, я думаю, для простых массивов вам нужно иметь другую специализацию, например шаблон void foo (const (& array) [Len]) ... или что-то в этом роде. – 2010-12-13 22:06:55

2

Моего выстрел будет что-то вроде этого:

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

Вы будете иметь различный operator() экземпляра для всего разыменовываемого с ->, так сырыми указателей и умных указателей.

+0

Умм, про этот базовый класс ... –

+0

Вы можете это сделать? Я думал, что первый аргумент шаблона для 'unary_function' должен был соответствовать типу аргумента' operator() '. –

+0

Нет, вы не можете. Да. –

8

Как насчет:

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

Хммм, мне это нравится. Однако на самом деле это не так уж и мало, чем 'IsEqual >' (и тогда мне не пришлось бы писать вывод). +1 в любом случае. –

+0

@Kristo: Хорошо, но теперь создайте шаблон 'isAllEqual', код придет. –

+0

Вы также можете дать 'IsEqual' аргумент шаблона по умолчанию:' template struct IsEqual ... 'И затем продолжайте использовать' IsEqual', как и раньше, для итераторов 'Foo *'. – aschepler

1

Вы могли бы, возможно, сделать что-то хитрое с неявными преобразованиями:

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

Но я бы на самом деле, а просто написать IsNotEqual.

0

Ответ Бена - это единственное, что вы можете сделать в C++ 03. В C++ 0x, хотя и/или с boost :: bind, вам не нужно наследовать от unary_function. Это позволяет использовать оператор templated(). Обычно вы можете уйти с C++ 03, но я думаю, что это технически неправильно.

+0

TR1 'bind' может быть в порядке. Мне нужно будет проверить с людьми, которые работают с нашим кодом. И неверно писать шаблонный 'operator()'. Это использование «не1», это ключевой момент. –

Смежные вопросы