[Pointer Container] Modification of pointer containers to allow const elements

The following code snippet: boost::ptr_vector<const int> v; v.push_back(new int); produces the following error message (Visual C++ 9, boost 1.44): boost\ptr_container\ptr_sequence_adapter.hpp(249) : error C2664: 'std::vector<_Ty,_Ax>::push_back' : cannot convert parameter 1 from 'const int *' to 'void *const &' The problem is that the underlying type of boost::ptr_vector<T> is always vector<void*>, no matter T is const. This prevents push_back() method from maintaining const-correctness. The rest of the pointer containers contain an equivalent problem and this has been a TODO for some time. According to Thorsten Ottosen (library author) ( https://svn.boost.org/trac/boost/ticket/3832 ): "The fix is basically just some meta-functions taking into account that T might be const, and if so, instantiate container<const void*> instead of container<void*>" To the vector problem, this means that the underlying type should be: vector<typename boost::mpl::if_<boost::is_const<T>, const void*, void*> > I have sucessfully applied this approach to the pointer containers that mimic STL counterparts (see attachment) and I guess it should work for the others. I need feedback on whether this is a completely correct approach and if so I would like to contribute a modification. Gerardo.

2010/10/14 Gerardo Hernández <g.hernandez@indizen.com>:
The problem is that the underlying type of boost::ptr_vector<T> is always vector<void*>, no matter T is const.
Since the point of this is to only ever instantiate one version of the underlying container, wouldn't it be better to always use underlying_container<void const*>, then const_cast when returning to the user? Since we know we had a pointer to non-const at some point, the const_casts would all be legal, afaik. (const_cast atop an underlying_container<void*> would not be.) ~ Scott

Scott McMurray <me22.ca+boost@gmail.com> wrote:
My point was to allow ptr containers of <const T> to work, maintaining const-correctness. I believe that requires different underlying container types and not only one as you suggest. So in this code snippet: boost::ptr_vector<const int> v; v.push_back(new int); v[0] = 3; the assignment shall produce a compiler error as v[0] would be const, to maintain const-correctness. With the underlying type defined as mpl::if_<boost::is_const<T>, const void*, void*>::type this is accomplished. But using void* const always as the underlying type and using const_cast during returning (and inserting) elements, would not const-correctness be lost? Gerardo. 2010/10/15 Scott McMurray <me22.ca+boost@gmail.com>:

On 15/10/10 10:41, Gerardo Hernández wrote:
I presume that Scott only intended to suggest abandoning const-correctness in the implementation, not the interface. Thus the above assignment would still cause an error. Since type safety has already been abandoned by making all pointers void*, const-correctness doesn't seem much of an additional sacrifice. John Bytheway

2010/10/15 John Bytheway <jbytheway+boost@gmail.com>:
I see the point. But I do not understand the advantage of using a single underlying type instead of two (which I presume was also Thorsten idea). Using always void* would not only require a lot of const_cast when returning data from the containers but also think that ptr_vector<T>::iterator must be of different type as ptr_vector<const T>::iterator, as: boost::ptr_vector<const int> v; v.push_back(new int); (*v.begin()) = 2; shall also be compiler error, as the container contains const elements. To sum up I think it is best to use the metafunction when instantiating the underlying type, as the implementation is simpler.

On Fri, Oct 15, 2010 at 13:04, Gerardo Hernandez <g.hernandez@indizen.com> wrote:
It's the same advantage as using the same underlying type for ptr_vector<int> and ptr_vector<string>: instantiating less code.
Using always void* would not only require a lot of const_cast when returning data from the containers
Not a big deal, since it already has to static_cast everywhere when returning data from the containers.
Again, ptr_vector<int> and ptr_vector<string> also need different iterators, despite using the same underlying type, so ptr_vector<int> and ptr_vector<const int> will have different iterator types as well, even though they'd both wrap the same internal iterator type. And just like above, they'll just need to const_cast in addition to the static_casts that are already there. ~ Scott

2010/10/15 Scott McMurray <me22.ca+boost@gmail.com>:
Ok. But static_cast are not present when when inserting. So methods like ptr_sequence_adapter::push_back or ptr_map_adapter::insert will see a const_cast added too.
Indeed. Accessors that return iterators could add const_cast too. So we have two options: 1) Scatter const_cast throught the code 2) Use mpl::if_ at the expense of one additional template instantiation (Would that be so painful?) Additional opinions are largely welcomed. Gerardo.

----- Original Message ----- From: "Gerardo Hernandez" <g.hernandez@indizen.com> To: <boost@lists.boost.org> Sent: Saturday, October 16, 2010 1:39 AM Subject: Re: [boost] [Pointer Container] Modification of pointer containers to allow const elements So we have two options: 1) Scatter const_cast throught the code 2) Use mpl::if_ at the expense of one additional template instantiation (Would that be so painful?) Additional opinions are largely welcomed. Gerardo. _______________________________________________ +1 for option 1). The internal implementation doesn't cares the user. One of the initial goals was to avoid template instantatiation, wasn't it? Vicente

2010/10/16 vicente.botet <vicente.botet@wanadoo.fr>:
+1 for option 1). The internal implementation doesn't cares the user. One of the initial goals was to avoid template instantatiation, wasn't it?
I didn't know that. So we have a winner. I will try to contact the mantainers for the fix. Gerardo.

2010/10/15 Gerardo Hernández <g.hernandez@indizen.com>:
That will still work with using internal containers that are always void const *. ptr_vector<T>'s operator[] would become something like this: T &operator[](size_type i) { return *const_cast<T*>(static_cast<T const *>(internal_container[i])); } That way if T is int const, it's returning an int const& -- and the const_cast is a NOP -- which will prevent v[0] = 3; as desired, but if T is non-const int, then it's returning an int& -- and the const_cast is a type conversion from int const* to int* that's legal because the invariant of the pointer container means that the pointer was of that type on insert, as inserters like push_back don't accept int const* in ptr_vector<int>, just like how we know that the static_cast is legal because of the invariant. Was that clearer? ~ Scott
participants (5)
-
Gerardo Hernandez
-
Gerardo Hernández
-
John Bytheway
-
Scott McMurray
-
vicente.botet