Re: [boost] Interest in a container which can hold multiple data types?
You're correct in that a vectorboost::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 vectorboost::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::vectorboost::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_beginstd::string(my_vec); itr !=
itr_endstd::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
On 5/5/2015 9:04 PM, James Armstrong wrote:
You're correct in that a vectorboost::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::vectorboost::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_beginstd::string(my_vec); itr != itr_endstd::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::vectorboost::any and std::listboost::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
On 5/6/2015 1:08 AM, James Armstrong wrote:
Do you mean something that would work along the lines of...
std::vectorboost::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_beginstd::string(my_vec); itr != itr_endstd::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::vectorboost::any and std::listboost::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::vectorboost::any and std::listboost::variant alike.
How would this differ from using boost.range filters?
eg
#include
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::vectorboost::any and std::listboost::variant alike. How would this differ from using boost.range filters?
eg #include
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::vectorboost::any and std::listboost::variant alike. How would this differ from using boost.range filters?
eg #include
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
On 5/5/2015 9:04 PM, James Armstrong wrote:
You're correct in that a vectorboost::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
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
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
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
On May 10, 2015 4:30:05 PM EDT, James Armstrong
On Sun, May 10, 2015 at 12:11 PM, Rob Stewart
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
void* get_boost_variant(boost::variant * 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
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_anystd::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
On 5/11/2015 10:25 AM, Rob Stewart wrote:
On May 10, 2015 4:30:05 PM EDT, James Armstrong
wrote: On Sun, May 10, 2015 at 12:11 PM, Rob Stewart
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
void* get_boost_variant(boost::variant * 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
On Tue, May 12, 2015 at 8:19 AM, Boris Rasin
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
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