Understanding const pointers to const value in C++ - c++

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.

Related

std::list<const SomeClass> can not be defined

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

How to enforce constness over template 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.

boost::shared_ptr and Return Type Resolver idiom

I am currently working on a concept of Object known in Java or C# for C++. It would be similar to variant type like boost::any, however having wider functionality. For that purpose I am using boost::shared_ptr to internaly store actual data and I wanted to provide Return Type Resolver idiom for easily obtaining this data, as it is stored in actual implementation. I know I could use boost::shared_ptr automatic conversion during assigment operator or constructor but as I said shared_ptr is not avaiable at this stage.
Implementing RtR I have encountered a problem with linux platform. For simplicity of code I provide just a simple code which reflects what I want to do basically and what is working under VS2010 and not under GCC. Any comments or solutions woud be appriciate.
struct RtR
{
template<typename Ptr>
operator Ptr()
{
return Ptr();
}
template<typename Ptr>
operator Ptr() const
{
return Ptr();
}
};
class TestRtR
{
void test()
{
boost::shared_ptr<int> intPtr(new int);
intPtr = get();
}
void test() const
{
boost::shared_ptr<const int> intPtr(new int);
intPtr = get();
}
RtR get()
{
RtR ret;
return ret;
}
const RtR get() const
{
const RtR ret;
return ret;
}
};
As I said - if You compile it under VS2010 everything goes ok, but under linux I get:
In member function ‘void TestRtR::test()':
error: ambiguous overload for ‘operator=’ in ‘intPtr = TestRtR::get()’
note: candidates are:
note: boost::shared_ptr<T>& boost::shared_ptr<T>::operator=(const boost::shared_ptr<T>&) [with T = int, boost::shared_ptr<T> = boost::shared_ptr<int>]
note: boost::shared_ptr<T>& boost::shared_ptr<T>::operator=(boost::shared_ptr<T>&&) [with T = int, boost::shared_ptr<T> = boost::shared_ptr<int>]
note: boost::shared_ptr<T>& boost::shared_ptr<T>::operator=(boost::detail::sp_nullptr_t) [with T = int, boost::shared_ptr<T> = boost::shared_ptr<int>, boost::detail::sp_nullptr_t = std::nullptr_t]
In member function ‘void TestRtR::test() const’:
error: ambiguous overload for ‘operator=’ in ‘intPtr = TestRtR::get()’
note: candidates are:
note: boost::shared_ptr<T>& boost::shared_ptr<T>::operator=(const boost::shared_ptr<T>&) [with T = const int, boost::shared_ptr<T> = boost::shared_ptr<const int>]
note: boost::shared_ptr<T>& boost::shared_ptr<T>::operator=(boost::shared_ptr<T>&&) [with T = const int, boost::shared_ptr<T> = boost::shared_ptr<const int>]
note: boost::shared_ptr<T>& boost::shared_ptr<T>::operator=(boost::detail::sp_nullptr_t) [with T = const int, boost::shared_ptr<T> = boost::shared_ptr<const int>, boost::detail::sp_nullptr_t = std::nullptr_t]
Are definitions of boost::shared_ptr different under GCC and VS2010? What is the ground of this ambiguity and how to solve it?

How to sort a std::set with const getters

I have a std::set container whose elements are objects of the following class:
class LaneConnector {
public:
const Lane* getLaneFrom() const {
return From;
}
const Lane* getLaneTo() const {
return To;
}
private:
Lane* From;
Lane* To;
}
and my comparator function is as follows:
struct MyLaneConectorSorter {
bool operator() (LaneConnector * c, LaneConnector * d)
{
Lane* a = const_cast<Lane*>(c->getLaneFrom());
Lane* b = const_cast<Lane*>(d->getLaneFrom());
return (a->getLaneID() < b->getLaneID());
}
} myLaneConnectorSorter;
Now when I try to sort the elements in the set with:
//dont panic, the container just came through a const_iterator of a std::map :)
const std::set<LaneConnector*> & tempLC = (*it_cnn).second;
std::sort(tempLC.begin(), tempLC.end(), myLaneConnectorSorter);
I get a frenzy of errors starting with the following lines, Appreciate if you help me solve this problem.
Thanks:
/usr/include/c++/4.6/bits/stl_algo.h: In function ‘void std::sort(_RAIter, _RAIter, _Compare) [with _RAIter = std::_Rb_tree_const_iterator<LaneConnector*>, _Compare = {anonymous}::MyLaneConectorSorter]’:
/home/.../dev/Basic/shared/conf/simpleconf.cpp:1104:65: instantiated from here
/usr/include/c++/4.6/bits/stl_algo.h:5368:4: error: no match for ‘operator-’ in ‘__last - __first’
/usr/include/c++/4.6/bits/stl_algo.h:5368:4: note: candidates are:
/usr/include/c++/4.6/bits/stl_iterator.h:321:5: note: template<class _Iterator> typename std::reverse_iterator::difference_type std::operator-(const std::reverse_iterator<_Iterator>&, const std::reverse_iterator<_Iterator>&)
/usr/include/c++/4.6/bits/stl_iterator.h:378:5: note: template<class _IteratorL, class _IteratorR> typename std::reverse_iterator<_IteratorL>::difference_type std::operator-(const std::reverse_iterator<_IteratorL>&, const std::reverse_iterator<_IteratorR>&)
/usr/include/c++/4.6/bits/stl_bvector.h:181:3: note: std::ptrdiff_t std::operator-(const std::_Bit_iterator_base&, const std::_Bit_iterator_base&)
/usr/include/c++/4.6/bits/stl_bvector.h:181:3: note: no known conversion for argument 1 from ‘std::_Rb_tree_const_iterator<LaneConnector*>’ to ‘const std::_Bit_iterator_base&’
First, you cannot sort an std::set. It is a sorted structure, sorting happens upon construction or insertion.
Second, you can construct an std::set with your own sorting functor, and you can avoid unnecessary const_casts by making it take const pointers:
struct MyLaneConectorSorter {
bool operator() (const LaneConnector* lhs, const LaneConnector* rhs) const
{
// you may want to put some null pointer checks in here
const Lane* a = lhs->getLaneFrom();
const Lane* b = rhs->getLaneFrom();
return a->getLaneID() < b->getLaneID();
}
};
and instantiate the set like this:
std::set<LaneConnector*, MyLaneConectorSorter> s(MyLaneConectorSorter());
or, if you want to construct it from a different set, with a different ordering,
std::set<LaneConnector*> orig = ..... ;
....
std::set<LaneConnector*, MyLaneConectorSorter> s(orig.begin(), orig.end(), MyLaneConectorSorter());

Casting a const reference to a const pointer to a const

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.