2011-05-17 18 views
8

estoy usando un recipiente para contener una lista de punteros a cualquier cosa:Comparar impulsar :: cualquier contenido

struct Example { 
    std::vector<boost::any> elements; 
} 

Para insertar elementos en este contenedor, que había escrito un par de funciones de ayuda (miembros de la struct Example):

void add_any(boost::any& a) { 
    elements.push_back(a); 
} 

template<typename T> 
void add_to_list(T& a) { 
    boost::any bany = &a; 
    add_any(bany); 
} 

Ahora, me gustaría insertar elementos solo cuando no están presentes en este contenedor. Para hacer esto, pensé que solo necesitaría llamar al search sobre elements con una función de comparador apropiada. Sin embargo, no sé cómo comparar las instancias boost::any.

Mi pregunta: Sabiendo que mis boost::any casos siempre contienen un puntero a algo; ¿es posible comparar dos valores de boost::any?


actualización

que gracias por sus respuestas. También he logrado hacer esto en un probablemente manera insegura: usando boost::unsafe_any_cast para obtener un void** y comparando el puntero subyacente.

Por el momento, esto está funcionando bien. Sin embargo, apreciaría sus comentarios: ¡quizás esto sea un gran error!

#include <boost/any.hpp> 
#include <iostream> 
#include <vector> 
#include <string> 
using namespace std; 

bool any_compare(const boost::any& a1, const boost::any& a2) { 
    cout << "compare " << *boost::unsafe_any_cast<void*>(&a1) 
     << " with: " << *boost::unsafe_any_cast<void*>(&a2); 
    return (*boost::unsafe_any_cast<void*>(&a1)) == 
     (*boost::unsafe_any_cast<void*>(&a2)); 
} 

struct A {}; 

class Example { 
public: 
    Example() : elements(0), 
       m_1(3.14), 
       m_2(42), 
       m_3("hello"), 
       m_4() {}; 
    virtual ~Example() {}; 

    void test_insert() { 
     add_to_list(m_1); 
     add_to_list(m_2); 
     add_to_list(m_3); 
     add_to_list(m_4); 
     add_to_list(m_1); // should not insert 
     add_to_list(m_2); // should not insert 
     add_to_list(m_3); // should not insert 
     add_to_list(m_4); // should not insert 
    }; 

    template <typename T> 
    void add_to_list(T& a) { 
     boost::any bany = &a; 
     add_any(bany); 
    } 

private: 
    vector<boost::any> elements; 
    double m_1; 
    int m_2; 
    string m_3; 
    A  m_4; 


    void add_any(const boost::any& a) { 
     cout << "Trying to insert " << (*boost::unsafe_any_cast<void*>(&a)) << endl; 
     vector<boost::any>::const_iterator it; 
     for (it = elements.begin(); 
      it != elements.end(); 
      ++it) { 
      if (any_compare(a,*it)) { 
       cout << " : not inserting, already in list" << endl; 
       return; 
      } 
      cout << endl; 
     } 
     cout << "Inserting " << (*boost::unsafe_any_cast<void*>(&a)) << endl; 
     elements.push_back(a); 
    }; 


}; 



int main(int argc, char *argv[]) { 

    Example ex; 
    ex.test_insert(); 
    unsigned char c; 
    ex.add_to_list(c); 
    ex.add_to_list(c); // should not insert 

    return 0; 
} 
+2

Nota al margen: Las funciones 'add_any' y' add_to_list' deberían tener una referencia constante como parámetro. Además, puede considerar usar un 'conjunto' en lugar de un' vector' si desea exclusividad. Aún necesitarías una función de comparación, por supuesto. –

+0

Buena pregunta. Probablemente no sea posible. Sin embargo, si está dispuesto a implementar * su propio * any, hay una forma de hacerlo (con * run time fail * si los tipos subyacentes son los mismos, pero no son compatibles con la semántica de comparación) –

+0

¿Está seguro de que ¿Desea almacenar la dirección del parámetro en el helper 'add_to_list'? Tenga en cuenta que 'boost :: any' copiará el objeto, pero en este caso el objeto es un puntero. –

Respuesta

3

La única manera fácil de hacer esto que puedo pensar implica el apoyo codificando para los tipos que se está almacenando en los casos any, minando la mayor parte de la utilidad de any ...

bool equal(const boost::any& lhs, const boost::any& rhs) 
{ 
    if (lhs.type() != rhs.type()) 
     return false; 

    if (lhs.type() == typeid(std::string)) 
     return any_cast<std::string>(lhs) == any_cast<std::string>(rhs); 

    if (lhs.type() == typeid(int)) 
     return any_cast<int>(lhs) == any_cast<int>(rhs); 

    // ... 

    throw std::runtime_error("comparison of any unimplemented for type"); 
} 

Con C++ 11 type_index puede usar std::map o std::unordered_map codificado en std::type_index(some_boost_any_object.type()) - similar a lo que sugiere Alexandre en su comentario a continuación.

+1

a 'map ' debería ser más escalable. –

+0

@Alexandre: aunque no funcionará de manera confiable ... lo que recuerdo es que puedes tener múltiples objetos type_info para el mismo tipo (por unidad de traducción, no garantizados para ser eliminados por el enlazador), por lo que debes comparar el ' type_info's con el 'operador ==' provisto ... incluso el mapeo de 'type_info-> name()' no es portable porque las implementaciones pueden proporcionar cualquier valor incluyendo una cadena vacía o un valor constante entre los tipos. 'type_info' es tan especial, ¡tienes que amarlo! –

+3

@Alexandre: 'std :: type_info :: before' –

4

No se puede proporcionar directamente, pero en realidad se puede utilizar any como el tipo subyacente ... aunque para los punteros que no tiene sentido (ah!)

struct any { 
    std::type_info const& _info; 
    void* _address; 
}; 

Y un constructor de plantilla:

template <typename T> 
any::any(T* t): 
    _info(typeid(*t)), 
    _address(dynamic_cast<void*>(t)) 
{ 
} 

Esto es, básicamente, boost::any.

Ahora tenemos que "aumentar" con nuestro mecanismo de comparación.

Para hacerlo, "capturaremos" la implementación de std::less.

typedef bool (*Comparer)(void*,void*); 

template <typename T> 
bool compare(void* lhs, void* rhs) const { 
    return std::less<T>()(*reinterpret_cast<T*>(lhs), *reinterpret_cast<T*>(rhs)); 
} 

template <typename T> 
Comparer make_comparer(T*) { return compare<T>; } 

y aumentar el constructor de any.

struct any { 
    std::type_info const& _info; 
    void* _address; 
    Comparer _comparer; 
}; 

template <typename T> 
any::any(T* t): 
    _info(typeid(*t)), 
    _address(dynamic_cast<void*>(t)), 
    _comparer(make_comparer(t)) 
{ 
} 

A continuación, le ofrecemos una especialización de less (o operator<)

bool operator<(any const& lhs, any const& rhs) { 
    if (lhs._info.before(rhs._info)) { return true; } 
    if (rhs._info.before(lhs._info)) { return false; } 
    return (*lhs._comparer)(lhs._address, rhs._address); 
} 

Nota: encapsulación, etc ... se deja como ejercicio para el lector

1

No es necesario crear una nueva clase. Intente utilizar xany https://sourceforge.net/projects/extendableany/?source=directory xany class permite agregar nuevos métodos a la funcionalidad existente de cualquiera. Por cierto, hay un ejemplo en la documentación que hace exactamente lo que quiere (crea comparable_todo).

+0

no parece compilar el sonido metálico de Apple LLVM versión 5.0 (sonido metálico-500.2.78) (basado en LLVM 3.3svn) Objetivo: x86_64-manzana-darwin13.0.0 también lo es la licencia para este código? – berkus

1

Si puede cambiar el tipo en el contenedor, hay Boost.TypeErasure. Proporciona una manera fácil de personalizar any. Por ejemplo, yo estoy usando tales typedef para fines similares:

#include <boost/type_erasure/any.hpp> 
#include <boost/type_erasure/operators.hpp> 

using Foo = boost::type_erasure::any< 
    boost::mpl::vector< 
     boost::type_erasure::copy_constructible<>, 
     boost::type_erasure::equality_comparable<>, 
     boost::type_erasure::typeid_<>, 
     boost::type_erasure::relaxed 
    > 
>; 

Foo se comporta exactamente igual que boost::any, excepto que puede ser comparado por la igualdad y el uso boost::type_erasure::any_cast en lugar de boost::any_cast.

Cuestiones relacionadas