I've run into some annoying issues with const-correctness in some templated code, that ultimately boils down to the following observation: for some reason, given an STL-ish Container type T, const typename T::pointer does not actually seem to yeild a constant pointer type, even if T::pointer is equivalent to T::value_type*.
The following example illustrates the problem. Suppose you have a templated function that takes a Container which must meet the STL Random Access Container concept requirements.
template <class Container>
void example(Container& c)
{
const typename Container::pointer p1 = &c[0]; // Error if c is const
const typename Container::value_type* p2 = &c[0];
}
Then, if we pass this function a const container...
const std::vector<int> vec(10);
example(vec);
...we get an invalid conversion from const int* to int*. But why is const typename Container::pointer not the same as const int* in this example?
Note that if I change const typename Container::pointer to simply typename Container::const_pointer it compiles fine, however, as far as I can tell, the const_pointer typedef is an extension, (I don't see it mentioned in the C++ standard Container Requirements (23.5, Table 65)), and so therefore I don't want to use it.
So how can I obtain a generic, const-correct pointer type from a container T? (I really can't see how to do this without using boost::mpl::if_ along with type_traits to check if the container is constant...but there must be a less verbose way to do this)
Edit: In case it matters, I'm using gcc 4.3.2 to compile this.
It doesn't work because your const does not apply to what you think it applies to. For example, if you have
typedef int* IntPtr;
then
const IntPtr p;
does not stand for
const int* p;
but rather stands for
int* const p;
Typedef-name is not a macro. Once the "pointerness" of the type is wrapped into a typedef-name, there's no way to use it to create a pointer-to-const type anymore. I.e. there's absolutely no way to use the above IntPtr typedef-name to produce an equivalent of
const int* p;
You have to either use to pointee type explicitly (as you did with value_type), or check whether your container defines a different typedef-name, with const already wrapped "inside" (like const_pointer or something like that).
This:
typename Container::pointer
Has the type int* (in our case). I don't know the terminology, so sorry for that, but pointers point to a type. That is, Container::pointer is a pointer to a mutable T, and adding const is only going to make this a const pointer (not a pointer to const), because Container::pointer has already been defined to point to a mutable T.
It seems only const_pointer, either from the class or your own:
typedef const typename Container::value_type* const_pointer
Will work.
The allocator requirements (cf. 20.1.5, Table 32, "Allocator requirements") state that allocators for the STL containers shall have an Allocator::const_pointer. All of the STL containers (the eight containers listed in Section 23) define a Container::const_pointer typedef as:
typedef typename Allocator::const_pointer const_pointer;
As you state in your question, however, containers (other than those eight) are not required to have a Container::const_pointer typedef.
This might be of relevance here (from SGI STL reference):
[6] As in the case of references [5], the pointer type must have the same semantics as C++ pointers but need not actually be a C++ pointer. "Smart pointers," however, unlike "smart references", are possible. This is because it is possible for user-defined types to define the dereference operator and the pointer member access operator, operator* and operator->.
It is possible by using a template. The trick is to remove the pointer first, then apply const on the type and finally applying pointer to the type - for instance using std::remove_pointer yields:
typedef const std::remove_pointer<Container::pointer>::type* PointerToConst;
Related
C++ STL uses a red-black tree to store data inside std::set and std::map. I noticed that set::iterator is actually a typedef of the const iterator of the red black tree:
//All code snippets taken from SGI STL. https://www.sgi.com/tech/stl/
typedef _Rb_tree<key_type, value_type, _Identity<value_type>, key_compare, _Alloc> _Rep_type;
typedef typename _Rep_type::const_iterator iterator;
This is reasonable because users are supposed not to modify the content of the set through an iterator. But set has to implement operations like insert and erase, which calls for a non-const iterator of the red-black tree. SGI STL uses a c-style cast to do this:
void erase(iterator __position) {
typedef typename _Rep_type::iterator _Rep_iterator;
_M_t.erase((_Rep_iterator&)__position);
}
I'm wondering:
Why is this cast safe? It is casting _Rep_type::const_iterator to _Rep_type::iterator&.
How to write the cast in C++ style? I've tried to do it: Neither static_cast nor const_cast will do the job. reinterpret_cast can compile, but I'm not sure if it does the same thing as the C-style cast.
iterator and _Rep_type::iterator are instances of the same class template, the former using const qualified types and the latter using the same, but non-const types. Something like this:
template <class T, class U>
struct B {};
using S = B<int&, int*>;
using Sconst = B<const int&, const int*>;
So for your questions:
It is safe because the two types have the exact same memory layout.
You cannot use static_cast because the compiler considers the types to be unrelated. You have to bring in the heavy artillery, reinterpret_cast:
int test() {
S s;
Sconst& sconst = reinterpret_cast<Sconst&>(s);
}
A C-Cast has no direct mapping to one specific C++ cast.
But a C-Cast can be mapped to one or more C++ casts.
When doing a C-Cast you can think of the compiler trying this set of C++ casts in order. The first one that is valid is what the C++ compiler will do: (see also C++ Standard, In N4917 7.6.3 Explicit type conversion (cast notation) [expr.cast])
const_cast
static_cast
static_cast followed by const_cast
reinterpret_cast
reinterpret_castfollowed by const_cast
So looking at your example, it looks like option 3 or 5 (but would need to try it with a compiler to be sure).
(_Rep_iterator&)__position
// Maps to:
const_cast<_Rep_iterator&>(static_cast<_Rep_iterator const&>(__position))
// or
const_cast<_Rep_iterator&>(reinterpret_cast<_Rep_iterator const&>(__position))
In the API of std::vector there are some typedefs and many functions which return these typedefs.
e.g.
reference operator[](size_type n);
Where reference and size_type are typedefs.
There is a typedef of pointer which it gets from it's allocator template argument. Why is the function signature of data() like this:
T* data() noexcept;
Rather than:
pointer data() noexcept;
Is there some reasoning behind this? Also why is it T* rather than value_type*.
If you want to check it is section 23.3.6.4 of the standard I have.
The reason data() exists is to get a pointer to the underlying array inside the vector, so that (for example) you can pass it to APIs that work with pointers not iterators.
The pointer typedef is not necessarily a real pointer type, it is a typedef for std::allocator_traits<allocator_type>::pointer which could be some class type that behaves like a pointer (sometimes called a "fancy pointer").
For the default case, std::vector<T> is std::vector<T, std::allocator<T>>, and std::allocator_traits<std::allocator<T>>::pointer is the same type as T*, so it makes no difference.
But for std::vector<T, CustomAllocator<T>> if data() returned a pointer you would not be able to pass it to a function expecting a T* unless is_same<pointer, T*>::value is true.
As of GCC 4.9.2, it's now possible to compile C++11 code that inserts or erases container elements via a const_iterator.
I can see how it makes sense for insert to accept a const_iterator, but I'm struggling to understand why it makes sense to allow erasure via a const_iterator.
This issue has been discussed previously, but I haven't seen an explanation of the rationale behind the change in behaviour.
The best answer I can think of is that the purpose of the change was to make the behaviour of const_iterator analogous to that of a const T*.
Clearly a major reason for allowing delete with const T* is to enable declarations such as:
const T* x = new T;
....
delete x;
However, it also permits the following less desirable behaviour:
int** x = new int*[2];
x[0] = new int[2];
const int* y = x[0];
delete[] y; // deletes x[0] via a pointer-to-const
and I'm struggling to see why it's a good thing for const_iterator to emulate this behaviour.
erase and insert are non-const member functions of the collection. Non-const member functions are the right way to expose mutating operations.
The constness of the arguments are irrelevant; they aren't being used to modify anything. The collection can be modified because the collection is non-const (held in the hidden this argument).
Compare:
template <typename T>
std::vector<T>::iterator std::vector<T>::erase( std::vector<T>::const_iterator pos );
^^^^^ ok
to a similar overload that is NOT allowed
template <typename T>
std::vector<T>::iterator std::vector<T>::erase( std::vector<T>::iterator pos ) const;
wrong ^^^^^
The first question here is whether constness of an iterator is supposed to imply constnes of the entire container or just constness of the elements being iterated over.
Apparently, it has been decided that the latter is the right approach. Constness of an iterator does not imply constness of the container, it only implies constness of container's elements.
Since the beginning of times, construction of an object and its symmetrical counterpart - destruction of an object - were considered meta-operations with regards to the object itself. The object has always been supposed to behave as mutable with regard to these operations, even if it was declared as const. This is the reason you can legally modify const objects in their constructors and destructors. This is the reason you can delete an object of type T through a const T * pointer.
Given the resolution referred to by 1, it becomes clear that 2 should be extended to iterators as well.
As title says, is it allowed? If not, are they sharing the same interface or abstract class anyway? I did not find any reference from online documents. but looks unordered_map and map are using the same functions.
No, they're totally different and even if you force it with reinterpret_cast, it'll just go horribly wrong at runtime (ie, the dreaded Undefined Behaviour).
They're both STL containers, so deliberately have consistent interfaces.
That doesn't mean they're the same thing internally.
They're two unrelated types. If you want to construct one based on the other, you need to use constructors that take iterator range (C++11):
template <class InputIterator>
map(InputIterator first, InputIterator last,
const Compare& comp = Compare(), const Allocator& = Allocator());
template <class InputIterator>
unordered_map(InputIterator f, InputIterator l,
size_type n = see below,
const hasher& hf = hasher(),
const key_equal& eql = key_equal(),
const allocator_type& a = allocator_type());
STL classes do not use virtual functions; there is no consistent base class you can cast through (ie, there's no equivalent to Java's java.util.Map). You can't cast between std::unordered_map and std::map any more than you could cast between HashMap and TreeMap in java - they're completely different types and cannot be used equivalently.
If you want a function to be able to take multiple types of STL containers, just use a template function:
template<typename Map>
void somefunc(Map &mymap) {
// ...
}
You can reinterpret cast one thing to any other thing, even if it makes no sense. The compiler won't stop you if you force it, which is essentially what reinterpret cast is. Casting map to unordered_map makes no sense & won't work.
Why do you want to cast map to unordered_map? They have the same interface. You gain nothing by casting it.
I have a member variable of type vector<T> (where is T is a custom class, but it could be int as well.)
I have a function from which I want to return a pointer to this vector, but I don't want the caller to be able to change the vector or it's items. So I want the return type to be const vector<const T>*
None of the casting methods I tried worked. The compiler keeps complaining that T is not compatible with const T.
Here's some code that demonstrates the gist of what I'm trying to do;
vector<int> a;
const vector<const int>* b = (const vector<const int>* ) (&a);
This code doesn't compile for me.
Thanks in advance!
If you have a const vector<int> you cannot modify the container, nor can you modify any of the elements in the container. You don't need a const vector<const int> to achieve those semantics.
On why a vector<T> cannot be correctly converted to a vector<const T> even if T can be converted to const T
This is a common recurring problem in programming whether it is with constness or inheritance (a container of derived object cannot be converted to a container of base objects, even if the contained elements themselves can). The problem is that element by element each one of them can be converted, but the container itself cannot without breaking the type system.
If you were allowed to do vector< const T > &vr = my_vector_of_T, then you would be allowed to add elements through vr, and those elements would be constant by definition. But at the same time those same elements would be aliased in my_vector_of_T as non-const elements and could be modified through that interface, breaking constness in the typesystem.
In the particular case of a vector<int> being converted to a vector<const int>, chances are that you would not notice really weird effects --besides adding an element to a vector<const int> and seeing how the constant element changes in time, but still remember that given two related types T1 and T2 for which a relation exists, in most cases trying to apply the same relationship to containers of T1 and T2 will break the type system.
In addition to James's answer about how to do it you should note that const int is not a valid type to put into any standard container since it is not assignable.
you can force conversion like this:
b = reinterpret_cast<const std::vector<const int>*>(&a);
but I do not think you should do this, since it is not guaranteed to work, only to compile
The compiler decides to block this. However, we know this is safe so maybe we can fool it:
const vector<const int>* b = (const vector<const int>* )(void *)(&a);