2010-12-13 14 views
8

Teniendo en cuenta lo siguiente:¿Puedo escribir un functor de C++ que acepte tanto un puntero sin formato como un puntero inteligente?

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

que tienen un contenedor de Foo* y utilizo std::find_if y std::not1 para averiguar si hay elementos en el contenedor donde bar() vuelve algo diferente de un valor dado. El código es el siguiente:

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

de avance rápido hacia el futuro y ahora tengo un recipiente diferente, esta vez contiene std::tr1::shared_ptr<Foo>. Me encantaría simplemente reutilizar mi functor en una versión sobrecargada de isAllEqual(). Pero no puedo. Foo* y shared_ptr<Foo> son tipos diferentes. Y necesito heredar desde unary_function para poder usar not1. Sería más elegante si pudiera evitar escribir el mismo funtor dos veces.

Preguntas:

  • ¿Hay alguna manera de escribir IsEqual por lo que se puede utilizar tanto punteros primas e inteligentes?
  • ¿Me esposé usando std::not1? ¿Debo simplemente escribir IsNotEqual?

Restricciones:

  1. que no pueden utilizar cualquier cosa de la biblioteca de impulso.
  2. Nuestro compilador no es lo suficientemente bueno para admitir C++ 0x lambdas.
+1

Esto suena como un ejemplo donde las plantillas serían agradables. – GWW

+0

@Kristo: ¿Su compilador es lo suficientemente bueno para proporcionar otras cosas de C++ 0x, como 'std :: begin'? –

+0

@Ben, estamos usando gcc 4.1.2, entonces probablemente no. 'std :: begin' y' std :: end' deben ser triviales para escribir. –

Respuesta

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 para verificar si hay un puntero nulo en 'operator()'. Me parece bien. –

+0

@Vlad: no funciona con buenos arreglos ordinarios antiguos. Además, ¿es una buena idea que 'unary_function :: Arg' sea diferente del tipo de parámetro de' operator()() '? –

+0

@Ben: Creo que para referencia constante al mismo tipo, está bien. Para admitir arrays, supongo que para matrices simples tienes que tener una especialización diferente, como la plantilla void foo (const (& array) [Len]) ... o algo así. –

2

Mi tiro sería algo como esto:

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

Vas a tener un operator() de instancias diferentes para todo dereferencable con ->, por lo punteros primas y punteros inteligentes.

+0

Umm, sobre esa clase base ... –

+0

¿Puedes hacer eso? Pensé que el primer argumento de plantilla para 'unary_function' tenía que coincidir con el tipo de argumento de' operator() '. –

+0

No, no puedes. Sí, lo hace. –

8

¿Qué tal:

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, me gusta. Sin embargo, no es menos tipeo que 'IsEqual >' (y luego no tendría que escribir el deducer). +1 de todos modos. –

+0

@Kristo: De acuerdo, pero ahora haga de 'isAllEqual' una plantilla también, el código viene. –

+0

También podría darle a 'IsEqual' un argumento de plantilla predeterminado: 'template struct IsEqual ...' Y luego continúe usando 'IsEqual' como antes para los iteradores a' Foo * '. – aschepler

1

Se podría tal vez hacer cosas delicadas con las conversiones implícitas:

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

Pero yo realmente algo acaba de escribir un IsNotEqual.

0

La respuesta de Ben es realmente lo único que puede hacer en C++ 03. Sin embargo, en C++ 0x y/o con boost :: bind, no es necesario heredar de unary_function. Esto le permite usar un operador con plantilla(). Por lo general, puedes salirte con la tuya en C++ 03 pero creo que es técnicamente incorrecto hacerlo.

+0

TR1 'bind' podría estar bien. Tendría que consultar con las personas que trabajan con nuestro código. Y no es incorrecto escribir un operador 'con plantilla()'. Es el uso de 'not1' que es el punto de fricción. –

Cuestiones relacionadas