Re: [boost] Interest in a container which can hold multiple data types?

You're correct in that a vector<boost::any> can achieve the same thing, and that would probably be the better way to implement this internally. I guess the proposal would be to provide the container interface to give some data modification functions and iterators which iterate over only a given type and such. That way there would be a standardized container with methods to deal with the data in a type specific way. -- James Armstrong

On 5/5/2015 9:04 PM, James Armstrong wrote:
You're correct in that a vector<boost::any> can achieve the same thing, and that would probably be the better way to implement this internally. I guess the proposal would be to provide the container interface to give some data modification functions and iterators which iterate over only a given type and such. That way there would be a standardized container with methods to deal with the data in a type specific way.
But why do you need special container type if all you want are algorithms and iterator adapters? Like I said before, this could be useful, but wouldn't it be better to write this as generic algorithms capable of working with any container of boost::any, not just vector? Better yet, any container of type erased or discriminated union objects (boost::any, boost::type_erasure::any, boost::variant, eggs::variant, etc.)?

Do you mean something that would work along the lines of... std::vector<boost::any> my_vec; // fill my_vec with various data types //iterate through doubles for (auto itr = itr_begin<double>(my_vec); itr != itr_end<double>(my_vec); ++itr) { // ... } //iterate through strings for (auto itr = itr_begin<std::string>(my_vec); itr != itr_end<std::string>(my_vec); ++itr) { // ... } where itr_begin and itr_end generate iterators for the templated type over any container of type erased objects. I didn't quite get what you were suggesting at first, but yeah I think I agree that would be more useful. On Tue, May 5, 2015 at 3:37 PM, Boris Rasin <boris@pointx.org> wrote:
On 5/5/2015 9:04 PM, James Armstrong wrote:
You're correct in that a vector<boost::any> can achieve the same thing, and that would probably be the better way to implement this internally. I guess the proposal would be to provide the container interface to give some data modification functions and iterators which iterate over only a given type and such. That way there would be a standardized container with methods to deal with the data in a type specific way.
But why do you need special container type if all you want are algorithms and iterator adapters? Like I said before, this could be useful, but wouldn't it be better to write this as generic algorithms capable of working with any container of boost::any, not just vector? Better yet, any container of type erased or discriminated union objects (boost::any, boost::type_erasure::any, boost::variant, eggs::variant, etc.)?
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
-- James Armstrong

On 5/6/2015 1:08 AM, James Armstrong wrote:
Do you mean something that would work along the lines of...
std::vector<boost::any> my_vec; // fill my_vec with various data types
//iterate through doubles for (auto itr = itr_begin<double>(my_vec); itr != itr_end<double>(my_vec); ++itr) { // ... }
//iterate through strings for (auto itr = itr_begin<std::string>(my_vec); itr != itr_end<std::string>(my_vec); ++itr) { // ... }
where itr_begin and itr_end generate iterators for the templated type over any container of type erased objects. I didn't quite get what you were suggesting at first, but yeah I think I agree that would be more useful.
Yes. And this would work with std::vector<boost::any> and std::list<boost::variant> alike.

I just took a look at boost::any and boost::variant (I haven't really worked with these, so unfamiliar) and it looks like this idea definitely do-able. I think that the restriction is that the container elements must implement a type() method so that when requesting one specific data type, we do not return another type which has been cast into the type requested. I think this is a more generally applicable method than designing one container which does this. I like the vector_tuple idea as well and think there is definite merit to storing data of the same type together in order to improve efficiency. In that scenario, I think there is no getting around designing a container/adapter specifically for that (I could be wrong). On Tue, May 5, 2015 at 4:23 PM, Boris Rasin <boris@pointx.org> wrote:
On 5/6/2015 1:08 AM, James Armstrong wrote:
Do you mean something that would work along the lines of...
std::vector<boost::any> my_vec; // fill my_vec with various data types
//iterate through doubles for (auto itr = itr_begin<double>(my_vec); itr != itr_end<double>(my_vec); ++itr) { // ... }
//iterate through strings for (auto itr = itr_begin<std::string>(my_vec); itr != itr_end<std::string>(my_vec); ++itr) { // ... }
where itr_begin and itr_end generate iterators for the templated type over any container of type erased objects. I didn't quite get what you were suggesting at first, but yeah I think I agree that would be more useful.
Yes. And this would work with std::vector<boost::any> and std::list<boost::variant> alike.
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
-- James Armstrong

On 05 May 2015 23:24 Boris Rasin [mailto:boris@pointx.org] wrote:
On 5/6/2015 1:08 AM, James Armstrong wrote:
Do you mean something that would work along the lines of...
Snip...
//iterate through doubles for (auto itr = itr_begin<double>(my_vec); itr != itr_end<double>(my_vec); ++itr) { // ... } Snip
Yes. And this would work with std::vector<boost::any> and std::list<boost::variant> alike.
How would this differ from using boost.range filters? eg #include <boost/range/adaptors.hpp> for (auto & val : boost::adaptors::filter(my_vec, []( boost::any & i) { return !i.empty() && i.type() == typeid(int); } ) ) { }

On 5/6/2015 2:24 PM, Alex Perry wrote:
On 05 May 2015 23:24 Boris Rasin [mailto:boris@pointx.org] wrote:
On 5/6/2015 1:08 AM, James Armstrong wrote:
Do you mean something that would work along the lines of...
Snip...
//iterate through doubles for (auto itr = itr_begin<double>(my_vec); itr != itr_end<double>(my_vec); ++itr) { // ... } Snip Yes. And this would work with std::vector<boost::any> and std::list<boost::variant> alike. How would this differ from using boost.range filters?
eg #include <boost/range/adaptors.hpp>
for (auto & val : boost::adaptors::filter(my_vec, []( boost::any & i) { return !i.empty() && i.type() == typeid(int); } ) ) { }
It wouldn't. Just a bit cleaner syntax, something like this: for (auto& val : type_filter<int>(my_vec)) { }

On 06 May 2015 12:44 Boris Rasin [mailto:boris@pointx.org] wrote:-
snip...
It wouldn't. Just a bit cleaner syntax, something like this:
for (auto& val : type_filter<int>(my_vec)) { }
Ok thanks - though I was hoping for something which was designed for performance here. Something along the lines of a boost.multiindex with the "primary" index being some sort of segmented collection with objects of the same size being stored together (so allowing faster access as per Joaquin's blog post referenced earlier [1]). That way heterogeneous objects could be pushed into the container as they arrive (and the order of arrival traversed via the 2ndary index) but faster traversal of all objects of the same type achieved (I'm hitting severe cache misses in a particular system and was looking for some solution other than pooling / bucket allocators etc) If it’s a cleaner syntax wanted though I agree my example using lambda's in a filter predicate is ugly - but templating the predicate up gives something very close to your syntax already template<typename T> bool IsType(boost::any & val) { return !val.empty() && val.type() == typeid(T); } for ( auto & val : boost::adaptors::filter( my_vec, IsType<int>() ) ) { } Or even cleaner using the | range operator (though possibly will need to wrap my_vec here - for some reason I rarely use the | syntax even though I like it in principle so have to play every time): for ( auto & val : my_vec | boost::adaptors::filtered( IsType<int>() ) ) { } Alex [1] http://bannalia.blogspot.co.uk/2014/05/fast-polymorphic-collections.html

Le 06/05/15 13:24, Alex Perry a écrit :
On 05 May 2015 23:24 Boris Rasin [mailto:boris@pointx.org] wrote:
On 5/6/2015 1:08 AM, James Armstrong wrote:
Do you mean something that would work along the lines of...
Snip...
//iterate through doubles for (auto itr = itr_begin<double>(my_vec); itr != itr_end<double>(my_vec); ++itr) { // ... } Snip Yes. And this would work with std::vector<boost::any> and std::list<boost::variant> alike. How would this differ from using boost.range filters?
eg #include <boost/range/adaptors.hpp>
for (auto & val : boost::adaptors::filter(my_vec, []( boost::any & i) { return !i.empty() && i.type() == typeid(int); } ) ) { }
I suspect the reason is efficiency? Vicente

I've given this a little more thought...So, I am not sure that generic algorithms like you suggest would be very beneficial. Ultimately, the type erased object are effectively pointers to memory in the heap. I'd think that any tangible benefit of allowing the user to choose their own container would be wiped out by the fact that the actual data is stored discontiguously out in the heap and you still need to dereference pointers to get to it. In this case, I think that defining the container but allowing the user to insert any datatype into natively would be more user friendly than allowing the use of any container but requiring the use of a type erased datatype. Also, to me it just feels 'cleaner' to call a method on the container rather than pass the container to a function. My current 2 cents On Tue, May 5, 2015 at 3:37 PM, Boris Rasin <boris@pointx.org> wrote:
On 5/5/2015 9:04 PM, James Armstrong wrote:
You're correct in that a vector<boost::any> can achieve the same thing, and that would probably be the better way to implement this internally. I guess the proposal would be to provide the container interface to give some data modification functions and iterators which iterate over only a given type and such. That way there would be a standardized container with methods to deal with the data in a type specific way.
But why do you need special container type if all you want are algorithms and iterator adapters? Like I said before, this could be useful, but wouldn't it be better to write this as generic algorithms capable of working with any container of boost::any, not just vector? Better yet, any container of type erased or discriminated union objects (boost::any, boost::type_erasure::any, boost::variant, eggs::variant, etc.)?
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
-- James Armstrong

On 5/6/2015 7:25 AM, James Armstrong wrote:
I've given this a little more thought...So, I am not sure that generic algorithms like you suggest would be very beneficial. Ultimately, the type erased object are effectively pointers to memory in the heap. I'd think that any tangible benefit of allowing the user to choose their own container would be wiped out by the fact that the actual data is stored discontiguously out in the heap and you still need to dereference pointers to get to it. In this case, I think that defining the container but allowing the user to insert any datatype into natively would be more user friendly than allowing the use of any container but requiring the use of a type erased datatype. Also, to me it just feels 'cleaner' to call a method on the container rather than pass the container to a function. My current 2 cents
boost::variant (unlike boost::any) stores data by value. As to what "feels cleaner": a single algorithm function which accepts any sequence container (std::vector, std::array, std::list, std::forward_list, boost::circular_buffer, etc.) with any type erased or discriminated union objects (boost::any, boost::type_erasure::any, boost::variant, eggs::variant, etc.) or a single specialized container type mimicking behavior of vector<any>?

On Wed, May 6, 2015 at 5:37 AM, Boris Rasin <boris@pointx.org> wrote:
boost::variant (unlike boost::any) stores data by value.
As to what "feels cleaner": a single algorithm function which accepts any sequence container (std::vector, std::array, std::list, std::forward_list, boost::circular_buffer, etc.) with any type erased or discriminated union objects (boost::any, boost::type_erasure::any, boost::variant, eggs::variant, etc.) or a single specialized container type mimicking behavior of vector<any>?
So, I went ahead and modified the code to be templated over a sequence container. You can see the code here, https://github.com/armstrhu/poly_adaptor poly_adaptor.h defined the class and main.cpp has some code with use examples. So, one issue is that the type erased types do not have the same methods to access their data. For instance, boost::any requires that you use a boost::any_cast<T> in order for you to get the value, whereas a boost::variant uses the get<T> function or you can access it with a stream. Basically, it will be hard to have a nice templated class for these, others, and newly developed type erased types without a common method to access the data. The other thing, as you mentioned above, is that this will have to be limited to sequence containers. It would be nice to come up with something fully general, but it again comes down to methods to access data. For example, the method for getting data from an std::map is very different than from a sequence container. Therefore, right now it seems this will be limited to sequence containers of boost::any only. Thoughts? Any ideas for a clean way around any of these? -- James Armstrong

On May 10, 2015 1:05:51 PM EDT, James Armstrong <armstrhu@gmail.com> wrote:
I went ahead and modified the code to be templated over a sequence container. You can see the code here,
https://github.com/armstrhu/poly_adaptor
poly_adaptor.h defined the class and main.cpp has some code with use examples.
So, one issue is that the type erased types do not have the same methods to access their data. For instance, boost::any requires that you use a boost::any_cast<T> in order for you to get the value, whereas a boost::variant uses the get<T> function or you can access it with a stream. Basically, it will be hard to have a nice templated class for these, others, and newly developed type erased types without a common method to access the data.
Create customization points. Implement the default access method and permit customization for other types. You could provide some common specializations in separate headers so users don't always have to reinvent the same wheel.
The other thing, as you mentioned above, is that this will have to be limited to sequence containers. It would be nice to come up with something fully general, but it again comes down to methods to access data. For example, the method for getting data from an std::map is very different than from a sequence container.
There is no easy means to bridge that gap. Associative containers require a key. ___ Rob (Sent from my portable computation engine)

On Sun, May 10, 2015 at 12:11 PM, Rob Stewart <rob.stewart@verizon.net> wrote:
Create customization points. Implement the default access method and permit customization for other types. You could provide some common specializations in separate headers so users don't always have to reinvent the same wheel.
So I worked on this a little bit. What I did was make a function pointer to allow the user to define their own access function. They could write something along the lines of template<typename T> void* get_boost_any(boost::any* data) { return (void*)boost::any_cast<T>(data); } template<typename U, typename T1, typename T2, ... typename TN> void* get_boost_variant(boost::variant<T1,T2,...,TN>* data) { return (void*)boost::get<U>(data); } The problem with this is there is a separate function for each T created so a single function pointer doesn't quite work. To get around that, I am thinking of having an array of function pointers, and the user will have to set the function pointer for each type explicitly. typedef data_type boost::any; // typedef data_type boost::variant<int, double, std::string> typedef container_type std::vector<data_type>; // typedef container_type std::list<data_type> container_type my_container; // fill my_container with data poly_adaptor<container_type, data_type> p(my_container); p.set_get_function(get_boost_any<int>); p.set_get_function(get_boost_any<double>); p.set_get_function(get_boost_any<std::string>); // p.set_get_function(get_boost_variant<int, int, double, std::string>); // p.set_get_function(get_boost_variant<double, int, double, std::string>); // p.set_get_function(get_boost_variant<std::string, int, double, std::string>); // now we can access data in p for int, double, and std::string //iterate over all ints for( auto itr = p.begin<int>(); itr != p.end<int>; ++p) { std::cout << *itr << std::endl } Interested in hearing ideas on a better implementation besides this. This is my first time really coding anything along these lines, so its really fun/interesting thinking through these issues. But, I am sure that lack of experience means I am overlooking some more elegant methods ;)

On May 10, 2015 4:30:05 PM EDT, James Armstrong <armstrhu@gmail.com> wrote:
On Sun, May 10, 2015 at 12:11 PM, Rob Stewart <rob.stewart@verizon.net> wrote:
Create customization points. Implement the default access method and permit customization for other types. You could provide some common specializations in separate headers so users don't always have to reinvent the same wheel.
So I worked on this a little bit. What I did was make a function pointer to allow the user to define their own access function. They could write something along the lines of
template<typename T> void* get_boost_any(boost::any* data) { return (void*)boost::any_cast<T>(data); }
template<typename U, typename T1, typename T2, ... typename TN> void* get_boost_variant(boost::variant<T1,T2,...,TN>* data) { return (void*)boost::get<U>(data); }
The problem with this is there is a separate function for each T created so a single function pointer doesn't quite work.
Those aren't function pointers. They are function templates. You're on the right track, but you're not using overloading to your advantage. You need a get function template that is overloaded for the various holder types. namespace custom { template<class T> T & get(boost::any & _value) { return boost:: any_cast<T>(_value); } } You then write code like the following for your access: custom::get<T>(value); Regardless of the value type, be it any, variant, or what have you, with an appropriate overload of get(), you'd be able to extract the value. IOW, your code determines the interface(s) needed for common interactions with the holder types, and then it relies on specializations and/or overloads to adapt to the holder types. If one wants to use a new type with your container, one must specialize or overload the customization points you've chosen. It is possible to capture all customization points into a single policy class, but that can be less flexible in some cases. I'm not saying it isn't appropriate for your case. I'm just saying it isn't the right thing by default. In the example above, I've exposed the calling code to an exception should the any_cast fail. You have to decide whether that's acceptable and, if not, require your own exception type or have get return T*. ___ Rob (Sent from my portable computation engine)

On Mon, May 11, 2015 at 1:25 AM, Rob Stewart <rob.stewart@verizon.net> wrote:
Those aren't function pointers. They are function templates. You're on the right track, but you're not using overloading to your advantage. You need a get function template that is overloaded for the various holder types.
namespace custom { template<class T> T & get(boost::any & _value) { return boost:: any_cast<T>(_value); } }
You then write code like the following for your access:
custom::get<T>(value);
Regardless of the value type, be it any, variant, or what have you, with an appropriate overload of get(), you'd be able to extract the value. IOW, your code determines the interface(s) needed for common interactions with the holder types, and then it relies on specializations and/or overloads to adapt to the holder types.
If one wants to use a new type with your container, one must specialize or overload the customization points you've chosen.
It is possible to capture all customization points into a single policy class, but that can be less flexible in some cases. I'm not saying it isn't appropriate for your case. I'm just saying it isn't the right thing by default.
In the example above, I've exposed the calling code to an exception should the any_cast fail. You have to decide whether that's acceptable and, if not, require your own exception type or have get return T*.
Sorry, I was spewing code without really saying much about it. I meant the part that sets function pointers for each templated instance of set_get_function<T> was, p.set_get_function(get_boost_any<int>); p.set_get_function(get_boost_any<double>); p.set_get_function(get_boost_any<std::string>); Internally, I have a vector of function pointers and the set_get_function (I've renamed this to add_accessor<T>) will add the function pointer to the vector. I see what you are saying about making a get function, it would work in a very similar fashion as get<>(std::tuple). However, I'd like to provide an iterator, and for that I need a function internal to the class which can get the transform the type-erased data to the native(?) data type. For example, my dereference operator needs a way to transform the boost::any/boost::variant/etc into the native data type. As is, I use the function pointer to use the user defined function to accomplish that. Without that, I am not sure if it is possible to provide an iterator since its not possible to know the method to transform any generic type-erased datatype. I am not aware of any other method to allow the user to insert a function that my class can use internally. Providing a custom::get<T>(value) I think is key, but allowing iteration with a clean syntax would be super nice as well. Currently, this is my syntax, //define templated accessor function template<typename T> void* boost_any_accessor(boost::any* b) { return (void*)boost::any_cast<T>(b); } int main() { typedef boost::any data_type data_type; typedef std::vector<data_type> container_type; container_type container; container.push_back(3.1415f); container.push_back(3.14159); container.push_back(std::string("a")); container.push_back(2.71828); container.push_back(std::string("b")); test::poly_adaptor<container_type> p(container); /* * Add data accessor function pointers * * An accessor function needs to be added for each data type to be * retrieved, but not necessarily for each type stored in container. */ p.add_accessor<std::string>(boost_any_accessor<std::string>); //provides access to std::string //iterate over std::strings for (auto itr = p.begin<std::string>(); itr != p.end<std::string>(); ++itr) { std::cout << *itr << std::endl; } std::cout << std::endl; } I think its a relatively clean syntax, but I'd take any suggestions for improvement, or clarification if I'm misunderstanding. I'll work on creating that get<T>(value) function also. Thanks for the feedback. -- James Armstrong

On 5/11/2015 10:25 AM, Rob Stewart wrote:
On May 10, 2015 4:30:05 PM EDT, James Armstrong <armstrhu@gmail.com> wrote:
On Sun, May 10, 2015 at 12:11 PM, Rob Stewart <rob.stewart@verizon.net> wrote:
Create customization points. Implement the default access method and permit customization for other types. You could provide some common specializations in separate headers so users don't always have to reinvent the same wheel. So I worked on this a little bit. What I did was make a function pointer to allow the user to define their own access function. They could write something along the lines of
template<typename T> void* get_boost_any(boost::any* data) { return (void*)boost::any_cast<T>(data); }
template<typename U, typename T1, typename T2, ... typename TN> void* get_boost_variant(boost::variant<T1,T2,...,TN>* data) { return (void*)boost::get<U>(data); }
The problem with this is there is a separate function for each T created so a single function pointer doesn't quite work. Those aren't function pointers. They are function templates. You're on the right track, but you're not using overloading to your advantage. You need a get function template that is overloaded for the various holder types.
namespace custom { template<class T> T & get(boost::any & _value) { return boost:: any_cast<T>(_value); } }
You then write code like the following for your access:
custom::get<T>(value);
Regardless of the value type, be it any, variant, or what have you, with an appropriate overload of get(), you'd be able to extract the value. IOW, your code determines the interface(s) needed for common interactions with the holder types, and then it relies on specializations and/or overloads to adapt to the holder types.
If one wants to use a new type with your container, one must specialize or overload the customization points you've chosen.
It is possible to capture all customization points into a single policy class, but that can be less flexible in some cases. I'm not saying it isn't appropriate for your case. I'm just saying it isn't the right thing by default.
In the example above, I've exposed the calling code to an exception should the any_cast fail. You have to decide whether that's acceptable and, if not, require your own exception type or have get return T*.
There is already well established customization point for this: #include <utility> try { using std::get; auto val = get<T>(value); catch(...) { // handle exception } This already works for std::tuple, std::pair and boost::variant. One could also add implementation for boost::any: #include <boost/any.hpp> namespace boost { template<typename T> inline T get(const any& a) { return any_cast<T>(a); } }

On Tue, May 12, 2015 at 8:19 AM, Boris Rasin <boris@pointx.org> wrote:
There is already well established customization point for this:
#include <utility>
try { using std::get; auto val = get<T>(value); catch(...) { // handle exception }
This already works for std::tuple, std::pair and boost::variant. One could also add implementation for boost::any:
#include <boost/any.hpp>
namespace boost { template<typename T> inline T get(const any& a) { return any_cast<T>(a); } }
Hey guys, thanks for the awesome feedback. I have to admit, I don't quite see how the class has access to the user defined overloads of get<T> if it hasn't explicitly been included in the class, but I tested it (defined get<T>(boost::any& a) in my main.cpp file) and it works beautifully. Most of the features I can think of are in now complete, I think the next step would be to use it and test it out and get any feedback including additional features to be included. The source can be gotten at https://github.com/armstrhu/poly_adaptor for anyone interested. Thanks again. -- James Armstrong
participants (5)
-
Alex Perry
-
Boris Rasin
-
James Armstrong
-
Rob Stewart
-
Vicente J. Botet Escriba