Compare boost::any contents
Question
I am using a container to hold a list of pointers to anything:
struct Example {
std::vector<boost::any> elements;
}
To insert elements in this container, I had written a couple of helper functions (members of the 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);
}
Now, I would like to insert elements only when they are not present in this container. To do this, I thought that I would only need to call search
over elements
with an appropriate comparator function. However, I do not know how to compare the boost::any
instances.
My question:
Knowing that my boost::any
instances always contain a pointer to something; is it possible to compare two boost::any
values?
update
I thank you for your answers. I have also managed to do this in a probably unsafe way: using boost::unsafe_any_cast
to obtain a void**
and comparing the underlying pointer.
For the moment, this is working fine. I would, however, appreciate your comments: maybe this is a big mistake!
#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;
}
Solution
The only easy way to do this I can think of involves hardcoding support for the types that you're storing in the any
instances, undermining much of the usefulness of 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");
}
With C++11's type_index
you could use a std::map
or std::unordered_map
keyed on std::type_index(some_boost_any_object.type())
- similar to what Alexandre suggests in his comment below.
OTHER TIPS
You cannot directly provide it, but you can actually use any
as the underlying type... though for pointers it's pointless (ah!)
struct any {
std::type_info const& _info;
void* _address;
};
And a templated constructor:
template <typename T>
any::any(T* t):
_info(typeid(*t)),
_address(dynamic_cast<void*>(t))
{
}
This is, basically, boost::any
.
Now we need to "augment" it with our comparison mechanism.
In order to do so, we'll "capture" the implementation of 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>; }
And augment the constructor of 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))
{
}
Then, we provided a specialization of less
(or 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);
}
Note: encapsulation, etc... are left as an exercise to the reader
If you can change type in container, there is Boost.TypeErasure. It provides easy way to customize any
. For example I'm using such typedef for similar purpose:
#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
behaves exactly the same as boost::any
, except that it can be compared for equality and use boost::type_erasure::any_cast
instead of boost::any_cast
.
There is no need to create new class. Try to use xany https://sourceforge.net/projects/extendableany/?source=directory xany class allows to add new methods to any's existing functionality. By the way there is a example in documentation which does exactly what you want (creates comparable_any).
Maybe this algorithm come in handy > http://signmotion.blogspot.com/2011/12/boostany.html
Compare two any-values by type and content. Attempt convert string to number for equals.