I am building an STL list. I made a decorator class (MyList) that is a list of a special class (ProtectMe). I want all of the items of the list to be const. So here's what I made:
#include <list>
using namespace std;
class ProtectMe{
private:
int data_;
public:
ProtectMe(int data):data_(data){
}
int data() const{return data_;}
};
class MyList{
private:
//A list of constant pointers to constant ProtectMes.
list<const ProtectMe* const> guts_;
public:
void add(const ProtectMe& data){
guts_.push_front(&data);
}
};
I get the following compile error:
error: ‘const _Tp* __gnu_cxx::new_allocator::address(const _Tp&) const [with _Tp = const ProtectMe* const]’ cannot be overloaded
I'm still scratching my head trying to decode where I went wrong. Why doesn't this code compile? What should I change?
The value_type of standard containers must be CopyInsertable (or MoveInsertable) in order for the push_front to work. The value type of list<const ProtectMe* const> is constant, so it's not CopyInsertable.
† CopyInsertable means that
allocator_traits<A>::construct(m, p, v);
is well defined where p is a pointer to value_type, which by default calls placement new on p and thus requires it to be a non-const pointer.
Related
I'm trying to define a constant list of constant objects and I can't seem to get it done.
Here is my example that compiles fine:
#include <string>
#include <list>
class Person { public:
std::string name;
Person(const std::string &in_name){name=in_name;}
};
class MyClass { public:
const std::list</* const */Person> l;
MyClass(const std::list</* const */Person> &in_l):l(in_l){}
};
int main(int argc, char **argv) {
Person dave("dave");
MyClass c(std::list<const Person>(dave));
return 0;
}
When I remove the comments from const in those 2 places,
I get the following errors:
In file included from /usr/include/x86_64-linux-gnu/c++/7/bits/c++allocator.h:33:0,
from /usr/include/c++/7/bits/allocator.h:46,
from /usr/include/c++/7/string:41,
from main66.cpp:1:
/usr/include/c++/7/ext/new_allocator.h: In instantiation of ‘class __gnu_cxx::new_allocator<const Person>’:
/usr/include/c++/7/bits/allocator.h:108:11: required from ‘class std::allocator<const Person>’
main66.cpp:11:53: required from here
/usr/include/c++/7/ext/new_allocator.h:93:7: error: ‘const _Tp* __gnu_cxx::new_allocator<_Tp>::address(__gnu_cxx::new_allocator<_Tp>::const_reference) const [with _Tp = const Person; __gnu_cxx::new_allocator<_Tp>::const_pointer = const Person*; __gnu_cxx::new_allocator<_Tp>::const_reference = const Person&]’ cannot be overloaded
address(const_reference __x) const _GLIBCXX_NOEXCEPT
^~~~~~~
/usr/include/c++/7/ext/new_allocator.h:89:7: error: with ‘_Tp* __gnu_cxx::new_allocator<_Tp>::address(__gnu_cxx::new_allocator<_Tp>::reference) const [with _Tp = const Person; __gnu_cxx::new_allocator<_Tp>::pointer = const Person*; __gnu_cxx::new_allocator<_Tp>::reference = const Person&]’
address(reference __x) const _GLIBCXX_NOEXCEPT
^~~~~~~
Is there any way to define a std::list of const objects?
Allocator-aware containers, such as std::list, cannot take const value types, because the Allocator requirements specify behavior only for cv-unqualified types. This means that the container is not guaranteed to be able to create the element objects through the allocator interface if the value type is const or volatile qualified.
This is not a problem though, because the container being const is enough to guarantee that the elements are not changed. If you access an element of the container through a const reference to the container, you will only ever get a const reference to the element.
So, just use const std::list<Person>, instead of const std::list<const Person>.
Technically someone could const_cast the constness away from such a reference to be able to modify the elements and that would probably be legal, i.e. not undefined behavior, but that is something that a user can always do, just that it would cause undefined behavior with const objects.
See also Does C++11 allow vector<const T>? for details.
std::list must have a non-const, non-volatile value_type.
Error message seems clear enough https://gcc.godbolt.org/z/MG3Kxv:
error: static assertion failed: std::list must have a non-const,
non-volatile value_type
template<typename T> struct SomeClass{
void someFunc(const T& data) const {}
};
void testFunc(const int* a) {
SomeClass<int*> some_class;
some_class.someFunc( a);
}
I made a template instance with a non-const type. Now when calling a certain function I get errors that say:
error: invalid conversion from ‘const int*’ to ‘int*’
note: initializing argument 1 of ‘void SomeClass<T>::someFunc(const T&) const [with T = int*]’
So basically my const T& is treated as plain T&, the const is ignored. Why? How can I make sure in this case that it is seen by the compiler as const T&?
You may want to consider to partial specialize your class template SomeClass for the case T is a pointer. Then, add const to the type pointed to instead of the pointer itself (i.e., pointer to const instead of const pointer):
template<typename T> struct SomeClass<T*> {
void someFunc(const T* &data) const { /* ... */ }
};
SomeClass<int*>::someFunc() (i.e., T = int*) will be instantiated to:
void someFunc(const int* &data) const;
data above is a reference to a pointer to const int. However, with your primary template, SomeClass<int*>::someFunc() is actually:
void someFunc(int* const &data) const;
That is, data here is a reference to a const pointer to int. Therefore, you can't pass a, which is a const int*, as an argument to someFunc() since that pointed const int would be modifiable through the parameter data. In other words, the constness would be lost.
You need to change your definition to SomeClass<const int*> some_class;. The T comes from the definition and is int*, compiler is complaining rightfully.
The const is not ignored. It's applied to the type int*, yielding an int* that cannot be modified, i.e., int* const. In const int*, the const applies to the int, not to the pointer. That is, const int* points at an int that cannot be modified.
Inside testFunc you end up both consts. Since it's called with a const int*, the specialization of SomeClass has to be SomeClass<const int*>. And then when you call someFunc you get the second one; the actual argument type is const int* const. The first const applies to the int and the second const applies to the argument itself, i.e., to the pointer.
Assuming that the code base is huge and therefore you can't afford to write a specialization for your class template, you could provide the following delegating member template, someFunc(), which is an overload of your original member function:
#include <type_traits>
template<typename T> struct SomeClass {
// your original member function
void someFunc(const T &data) const { /* ... a lot of stuff ... */ }
// delegating member template
template<typename S>
void someFunc(const S* &data) const {
// delegate to original function
someFunc(const_cast<S*>(data));
}
};
First, this member template only comes into play with pointer arguments. Second, what it really does is to delegate the call to your original member function with the same name by casting out the const from the pointed type.
I hope it helps.
I have two questions.
First of all I have a little problem with the understanding of const pointers to const values. I don't get why B::insert works, while C::insert results in a compiler error. I mean doesn't the list in C exactly equals the parameter of C::insert?
My second question is whether A const * const a, could also be written as const A& a.
class A
{
//Do stuff
};
class B
{
private:
list<A const *> l;
public:
void insert(A const * const a)
{
l.push_back(a);
}
};
class C
{
private:
list<A const * const> l;
public:
void insert(A const * const a)
{
l.push_back(a);
}
};
Edit (Compile error):
g++ -Wall -c -O2 "sonnensystem.cpp" -std=c++11 (im Verzeichnis: C:\Users\Kenan\Desktop\OPR\cppcode\Konzepte\Kapselung\Architektur\sonnensystem01)
In file included from C:/TDM-GCC-64/lib/gcc/x86_64-w64-mingw32/5.1.0/include/c++/x86_64-w64-mingw32/bits/c++allocator.h:33:0,
from C:/TDM-GCC-64/lib/gcc/x86_64-w64-mingw32/5.1.0/include/c++/bits/allocator.h:46,
from C:/TDM-GCC-64/lib/gcc/x86_64-w64-mingw32/5.1.0/include/c++/string:41,
from C:/TDM-GCC-64/lib/gcc/x86_64-w64-mingw32/5.1.0/include/c++/bits/locale_classes.h:40,
from C:/TDM-GCC-64/lib/gcc/x86_64-w64-mingw32/5.1.0/include/c++/bits/ios_base.h:41,
from C:/TDM-GCC-64/lib/gcc/x86_64-w64-mingw32/5.1.0/include/c++/ios:42,
from C:/TDM-GCC-64/lib/gcc/x86_64-w64-mingw32/5.1.0/include/c++/ostream:38,
from C:/TDM-GCC-64/lib/gcc/x86_64-w64-mingw32/5.1.0/include/c++/iostream:39,
from sonnensystem.cpp:1:
C:/TDM-GCC-64/lib/gcc/x86_64-w64-mingw32/5.1.0/include/c++/ext/new_allocator.h: In instantiation of 'struct __gnu_cxx::new_allocator<const A* const>':
C:/TDM-GCC-64/lib/gcc/x86_64-w64-mingw32/5.1.0/include/c++/bits/allocator.h:92:11: required from 'class std::allocator<const A* const>'
C:/TDM-GCC-64/lib/gcc/x86_64-w64-mingw32/5.1.0/include/c++/bits/stl_list.h:315:9: required from 'class std::__cxx11::_List_base<const A* const, std::allocator<const A* const> >'
C:/TDM-GCC-64/lib/gcc/x86_64-w64-mingw32/5.1.0/include/c++/bits/stl_list.h:507:11: required from 'class std::__cxx11::list<const A* const>'
sonnensystem.cpp:28:27: required from here
C:/TDM-GCC-64/lib/gcc/x86_64-w64-mingw32/5.1.0/include/c++/ext/new_allocator.h:93:7: error: 'const _Tp* __gnu_cxx::new_allocator<_Tp>::address(__gnu_cxx::new_allocator<_Tp>::const_reference) const [with _Tp = const A* const; __gnu_cxx::new_allocator<_Tp>::const_pointer = const A* const*; __gnu_cxx::new_allocator<_Tp>::const_reference = const A* const&]' cannot be overloaded
address(const_reference __x) const _GLIBCXX_NOEXCEPT
^
C:/TDM-GCC-64/lib/gcc/x86_64-w64-mingw32/5.1.0/include/c++/ext/new_allocator.h:89:7: error: with '_Tp* __gnu_cxx::new_allocator<_Tp>::address(__gnu_cxx::new_allocator<_Tp>::reference) const [with _Tp = const A* const; __gnu_cxx::new_allocator<_Tp>::pointer = const A* const*; __gnu_cxx::new_allocator<_Tp>::reference = const A* const&]'
address(reference __x) const _GLIBCXX_NOEXCEPT
^
Kompilierung fehlgeschlagen.
In your declaration A const * const, the first const says that the A * pointer points to a value that can't be changed (a const pointer). The second const says that the value of that pointer can't be changed, just like a const int can't be changed. Since list (and other standard containers) require their members to be assignable, they can't be const values.
For your second question, a A const * const and const A& are similar, but not interchangeable, as the ways you use them are different.
When using std::list<T>, one of the requirements for T is that it is CopyAssignable. See http://en.cppreference.com/w/cpp/container/list.
When you use a const type as the parameter, that requirement is not met. You will see a similar error, if not the same error, if you use:
std::list<const int> a;
a.push_back(10);
Anyway,
list<A const * const> l;
is not usable.
The only real difference between A const * const and A const & is that it is easier to check that the pointer is invalid (you could cast a nul pointer to A and then dereference to get a null A reference, it's just easier to go if(!a)).
The C::insert case is due to the code trying to assign to the internal node value. It would probably work if you used emplace_back instead of push_back.
I am trying to figure out SGI STL recently, and I write my own Vector, but I have trouble with a simple test code. when I tried to compile it, it complaint like this:
error: passing ‘const Vector<int>’ as ‘this’ argument of
‘Vector<T,Alloc>::value_type* Vector<T, Alloc>::end()
[with T = int; Alloc = __default_alloc_template<false, 0>; Vector<T, Alloc>::iterator = int*; Vector<T, Alloc>::value_type = int]’
discards qualifiers [-fpermissive]
size_type size() const { return size_type(end() - begin()); }
I have looked up in the usage of const function, it says that if the code does not change any member in the class, then it can be a const function.
I really do not understand, the size() does not change any member in the Vector, just invokes two other functions.
I checked SGI_vector, and I think it is quite the same to my code.
What's wrong with it? Thank you!
int main(){
Vector<int> v2;
cout<<"sizeof(v2): "<<v2.size()<<endl;
return 0;
}
I have write my own Vector like this:
template <class T, class Alloc = alloc>
class Vector {//primary template
public:
typedef T value_type;
typedef value_type* iterator;
typedef size_t size_type;
protected:
typedef simple_alloc<value_type,alloc> data_allocator;
iterator start;
iterator finish;
iterator end_of_storage;
public:
iterator begin(){return start;}
iterator end() { return finish; }
size_type size() const { return size_type(end() - begin()); }
Vector():start(0),finish(0), end_of_storage(0){}
};
size() is a const member function. From this function, you can only call other const member functions. The calls to begin() and end() in size() are not allowed since begin() and end() are non-const member functions.
You can just use the member variables start and finish to implement size().
size_type size() const { return size_type(finish - start); }
I am writing a thin template wrapper for iterators, and hit a stumbling block when passing through the structure dereference operator, mainly because pointers don't have one:
#include <vector>
struct mystruct {
int member;
};
template<class iterator>
struct wrap {
typedef typename std::iterator_traits<iterator>::pointer pointer;
iterator internal;
pointer operator->() {return internal.operator->();} //MARK1
};
int main() {
wrap<std::vector<mystruct>::iterator> a;
a->member;
wrap<mystruct*> b;
b->member;
return 0;
}
http://ideone.com/XdvEz
prog.cpp: In member function ‘typename std::iterator_traits<_Iter>::pointer wrap<iterator>::operator->() [with iterator = mystruct*]’:
prog.cpp:18: instantiated from here
prog.cpp:11: error: request for member ‘operator->’ in ‘((wrap<mystruct*>*)this)->wrap<mystruct*>::internal’, which is of non-class type ‘mystruct*’
This following method works, but I don't think it's guaranteed to work. Namely, if an iterator has a strange pointer type that isn't the same as a pointer to a value_type.
pointer operator->() {return &*internal;} //MARK3
The standard indirectly says that an overloaded operator-> has to either return a pointer, an object that is convertible to a pointer, or an object that has overloaded operator->. Your best bet is to just return internal.
§13.5.6 [over.ref] p1
An expression x->m is interpreted as (x.operator->())->m
(The above applies recursively.)