The const here is the cause of the compilation problem. However, having implemented an AVL tree myself, I can't understand why.
Here's the code:
#include <set>
int main ()
{
int a;
// I want the set to carry the "promise"
// to keep the pointers constant
std::set<int * const> x;
x.insert(&a);
}
And here's the error:
In file included from /usr/include/c++/7/string:48:0,
from /usr/include/c++/7/bits/locale_classes.h:40,
from /usr/include/c++/7/bits/ios_base.h:41,
from /usr/include/c++/7/ios:42,
from /usr/include/c++/7/ostream:38,
from /usr/include/c++/7/iostream:39,
from demo.cpp:1:
/usr/include/c++/7/bits/stl_function.h: In instantiation of ‘struct std::_Identity<int* const>’:
/usr/include/c++/7/bits/stl_tree.h:2091:29: required from ‘std::pair<std::_Rb_tree_iterator<_Val>, bool> std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_M_insert_unique(_Arg&&) [with _Arg = int* const; _Key = int* const; _Val = int* const; _KeyOfValue = std::_Identity<int* const>; _Compare = std::less<int* const>; _Alloc = std::allocator<int* const>]’
/usr/include/c++/7/bits/stl_set.h:510:48: required from ‘std::pair<typename std::_Rb_tree<_Key, _Key, std::_Identity<_Key>, _Compare, typename __gnu_cxx::__alloc_traits<_Alloc>::rebind<_Key>::other>::const_iterator, bool> std::set<_Key, _Compare, _Alloc>::insert(std::set<_Key, _Compare, _Alloc>::value_type&&) [with _Key = int* const; _Compare = std::less<int* const>; _Alloc = std::allocator<int* const>; typename std::_Rb_tree<_Key, _Key, std::_Identity<_Key>, _Compare, typename __gnu_cxx::__alloc_traits<_Alloc>::rebind<_Key>::other>::const_iterator = std::_Rb_tree_const_iterator<int* const>; std::set<_Key, _Compare, _Alloc>::value_type = int* const]’
demo.cpp:11:18: required from here
/usr/include/c++/7/bits/stl_function.h:877:7: error: ‘const _Tp& std::_Identity<_Tp>::operator()(const _Tp&) const [with _Tp = int* const]’ cannot be overloaded
operator()(const _Tp& __x) const
^~~~~~~~
/usr/include/c++/7/bits/stl_function.h:873:7: error: with ‘_Tp& std::_Identity<_Tp>::operator()(_Tp&) const [with _Tp = int* const]’
operator()(_Tp& __x) const
Is there a "clean" way to do this? (ie. not a work-around like making a "pointer class" with a comparator for every situation like this)
You cannot modify elements stored in an std::set so the point is moot. It is designed to keep elements in a sorted order and modifications would break that guarantee. That's why the iterators (both std::set<T>::iterator and std::set<T>::const_iterator) both return const references.
There is no way to edit an element short of mutable (or const_cast), in which case you still need to guarantee the ordering remains the same.
More formal answer is that std::set meets the requirements of being AllocatorAwareContainer:
A set satisfies all of the requirements of a container, of a
reversible container ([container.requirements]), of an associative
container ([associative.reqmts]), and of an allocator-aware container
(Table 65).
and in [allocator.requirements] in table 33 you can read:
T, U, C any cv-unqualified object type ([basic.types])
where T is same as X::value_type where X is an allocator class for type T. This means std::allocator<int * const> does not meets above requirements.
This is true for many other containers, for example vector, you may read more here: Does C++11 allow vector<const T>?
[edit[
Visual Studio gives slightly more descriptive error:
C:\Program Files (x86)\Microsoft Visual Studio
14.0\VC\INCLUDE\xmemory0(585): error C2338: The C++ Standard forbids containers of const elements because allocator is ill-formed.
with clang you may see that first error lines directs to allocator headers:
../include/c++/5.5.0/ext/new_allocator.h:93:7: error: multiple overloads of 'address' instantiate to the same signature '__gnu_cxx::new_allocator::const_pointer (__gnu_cxx::new_allocator::const_reference) const noexcept' (aka 'int *const *(int *const &) const noexcept')
address(const_reference __x) ........
Here's a simple program to demonstrate the problem you are seeing:
int main(int argc, char ** argv)
{
int * const a = NULL;
int * const b = NULL;
b = a; // error: cannot assign to variable 'b' with const-qualified type
}
Note that it's a compile-time error to change the value of a variable of int * const, because the variable is considered read-only.
std::set internally has the same problem -- it needs to modify variables of the specified type, and it cannot do so if its specified type is read-only.
Changing the type to const int * instead is probably what you want to do, as that type allows the pointers to be overwritten when necessary (while not allowing modifications to the ints that they point to).
Related
I am trying to define a map from member function pointers to strings in C++. But the the compiler complains that operator < is not defined for member function pointers. Is there a way to do this?
Example code:
#include <map>
#include <string>
class foo;
typedef void (foo::*fooFunc)();
class foo {
public:
foo() {
myMap[func]="example function";
}
void func() {};
std::map<fooFunc,std::string> myMap;
};
Resulting error message:
In file included from C:/msys64/mingw64/include/c++/6.2.0/bits/stl_tree.h:65:0,
from C:/msys64/mingw64/include/c++/6.2.0/map:60,
from grr.cpp:1:
C:/msys64/mingw64/include/c++/6.2.0/bits/stl_function.h: In instantiation of 'constexpr bool std::less<_Tp>::operator()(const _Tp&, const _Tp&) const [with _Tp = void (foo::*)()]':
C:/msys64/mingw64/include/c++/6.2.0/bits/stl_map.h:501:32: required from 'std::map<_Key, _Tp, _Compare, _Alloc>::mapped_type& std::map<_Key, _Tp, _Compare, _Alloc>::operator[](std::map<_Key, _Tp, _Compare, _Alloc>::key_type&&) [with _Key = void (foo::*)(); _Tp = std::__cxx11::basic_string<char>; _Compare = std::less<void (foo::*)()>; _Alloc = std::allocator<std::pair<void (foo::* const)(), std::__cxx11::basic_string<char> > >; std::map<_Key, _Tp, _Compare, _Alloc>::mapped_type = std::__cxx11::basic_string<char>; std::map<_Key, _Tp, _Compare, _Alloc>::key_type = void (foo::*)()]'
grr.cpp:9:15: required from here
C:/msys64/mingw64/include/c++/6.2.0/bits/stl_function.h:386:20: error: invalid operands of types 'void (foo::* const)()' and 'void (foo::* const)()' to binary 'operator<'
{ return __x < __y; }
~~~~^~~~~
std::map requires a comparison operator for its keys. By default, it uses operator <. Since operator < is not defined for pointer-to-member types, you are getting this error.
To fix it you'll need to define a custom comparison operator and provide it as a parameter to std::map. Alternatively, use a different container.
From cppreference:
std::map is a sorted associative container that contains key-value
pairs with unique keys. Keys are sorted by using the comparison
function Compare. Search, removal, and insertion operations have
logarithmic complexity. Maps are usually implemented as red-black
trees
If you want options on how to write such a comparison operator, it has already been answered here.
I have a complex map that, in the end, stores pointers to Drawable objects. Drawable objects have a draw() member function which is declared as const. I need to call all the draw functions for all objects stored in my map that are of a certain type, and I must do it inside a const function. However I can't seem to be able to preserve the const-correctness of my function (drawSolid).
My outer map (map<int, ##>) is essentially indexing some sub-maps. Sub-maps are, in turn, indexing vectors (map<ItemType, vector<##> >). Finally, this vector keeps a set of shared_ptr<Drawable> objects.
If I remove the const qualifier from my function header, everything compiles, but I need it to be const. How may I iterate through my multidimensional map, preserving const-correctness?
void DrawableItems::drawSolid(int item_list = -1) const
{
typedef std::map<int, std::map<ItemType, std::vector<std::shared_ptr<Drawable> > > > drawablemap;
std::vector<std::shared_ptr<Drawable> > its;
for(drawablemap::const_iterator li = __items.begin(); li != __items.end(); li++) {
its = li->second[SOLID];
for(auto di = its.begin(); di != its.end(); di++) {
di->get()->draw();
}
}
}
This the error I get from the compiler (G++):
/.../dss-sim/src/graphics/DrawableItems.cpp: In member function ‘void DrawableItems::drawSolid(int) const’:
/.../dss-sim/src/graphics/DrawableItems.cpp:51:35: error: passing ‘const std::map<DrawableItems::ItemType, std::vector<std::shared_ptr<Drawable> > >’ as ‘this’ argument discards qualifiers [-fpermissive]
its = li->second[SOLID];
^
In file included from /usr/include/c++/5/map:61:0,
from /.../dss-sim/src/common/dss.hpp:11,
from /.../dss-sim/src/graphics/DrawableItems.hpp:19,
from /.../dss-sim/src/graphics/DrawableItems.cpp:15:
/usr/include/c++/5/bits/stl_map.h:494:7: note: in call to ‘std::map<_Key, _Tp, _Compare, _Alloc>::mapped_type& std::map<_Key, _Tp, _Compare, _Alloc>::operator[](std::map<_Key, _Tp, _Compare, _Alloc>::key_type&&) [with _Key = DrawableItems::ItemType; _Tp = std::vector<std::shared_ptr<Drawable> >; _Compare = std::less<DrawableItems::ItemType>; _Alloc = std::allocator<std::pair<const DrawableItems::ItemType, std::vector<std::shared_ptr<Drawable> > > >; std::map<_Key, _Tp, _Compare, _Alloc>::mapped_type = std::vector<std::shared_ptr<Drawable> >; std::map<_Key, _Tp, _Compare, _Alloc>::key_type = DrawableItems::ItemType]’
operator[](key_type&& __k)
There is no const version of operator[] in std::map. However, there is a const version of at() which you can use instead:
its = li->second.at(SOLID);
The reason is that operator[] inserts an element if there is no element yet, so there can not be a const version of operator[].
at() on the other hand throws an exception if no element exists, and this is compatible with a const std::map.
I have a map of pointer to member declared as :
std::map<char, T (Operand::*)(const T &, const T &)> op_map;
I fill my map with pointer to member directly in the constructor of my class with :
op_map['+'] = &Operand::op_add;
For example, op_add source code is :
T op_add(const T & a, const T & b) {
return a + b;
}
And I want to call my pointer to member from a const function. Here is the source code :
IOperand *res_int32(char op, const IOperand & rhs) const {
IOperand *res = const_cast<IOperand *>(&rhs);
Operand<int> *tmp = dynamic_cast<Operand<int>*>(res);
T res_calc = (this->*op_map[op])(_value, (T)tmp->getValue());
}
But it makes me always an error :
Operand.hpp:70:64: error: passing ‘const std::map<char, double (Operand<double>::*)(const double&, const double&), std::less<char>, std::allocator<std::pair<const char, double (Operand<double>::*)(const double&, const double&)> > >’ as ‘this’ argument of ‘std::map<_Key, _Tp, _Compare, _Alloc>::mapped_type& std::map<_Key, _Tp, _Compare, _Alloc>::operator[](const key_type&) [with _Key = char, _Tp = double (Operand<double>::*)(const double&, const double&), _Compare = std::less<char>, _Alloc = std::allocator<std::pair<const char, double (Operand<double>::*)(const double&, const double&)> >, std::map<_Key, _Tp, _Compare, _Alloc>::mapped_type = double (Operand<double>::*)(const double&, const double&), std::map<_Key, _Tp, _Compare, _Alloc>::key_type = char]’ discards qualifiers [-fpermissive]
Operand.hpp:70:64: error: invalid conversion from ‘const Operand<double>* const’ to ‘Operand<double>*’ [-fpermissive]
Have you got any solution ?
Thank you.
operator[] can't be applied to a const map, since it inserts a new element if the key is not found.
In C++11, there is an at function which throws an exception if the key is not found:
T res_calc = (this->*op_map.at(op))(_value, (T)tmp->getValue());
^^^^^^^
In C++03, you'll need to use find:
map_type::const_iterator found = op_map.find(op);
if (found != op_map.end()) {
T res_calc = (this->*(found->second))(_value, (T)tmp->getValue());
} else {
// handle error
}
You'll also need to change the type of the member functions in the map to
T (Operand::*)(const T &, const T &) const
^^^^^
in order to call them on this from a const member function.
Just make op_add a const member function.
T op_add(const T & a, const T & b) const // <<<
{
return a + b;
}
And instead of the std::map::operator[] use std::map::find http://www.cplusplus.com/reference/stl/map/find/
EDIT:
You also need to change the map type to std::map<char, T (Operand::*)(const T &, const T &) const> op_map, as correctly pointed by R. Martinho Fernandes.
If you know what you are doing, you can try to compile with the c++ flag -fpermissive as G++ said.
I'm getting a compiling error for the code below, after removing the comment characters from the first insert line. I'm unable to insert the structure into the map while inserting integers is fine.
# include <iostream>
# include <map>
using namespace std;
struct node
{int test;} temp;
int main()
{
temp.test = 24;
int test = 30;
map<node, bool> mymap1;
map<int, bool> mymap2;
//mymap1.insert(make_pair(temp, true));
mymap2.insert(make_pair(test, true));
return 0;
}
How can I fix the error?
For a type to serve as the key for a map, it has to be ordered. All that means, practically, is that operator< must be defined for the type. If you defined a global operator<(const node&, const node&), this should work fine; i.e.,
bool operator<(const node& n1, const node& n2) {
return n1.test < n2.test;
}
A std::map's keys are stored internally in a binary search tree. In order for keys to be stored and searched for in a binary search tree, they must be comparable. For example, a requirement of a binary search tree is that a left child's key is less than its parent's key and a right child's key is greater than its parent's key. However, if the keys are not comparable, how are we supposed to tell whether the children are greater or less than the parent? We cannot form a tree and therefore std::map will not work with these types.
You simply need to define the less than operator like so:
bool operator<(const node& n1, const node& n2)
{
return n1.test < n2.test;
}
This also must be a friend of your node struct if the "test" data member is private (It's public now since node is currently a struct). However, I would probably do it like this:
#include <map>
class node
{
public:
int getTest() const { return _test; }
void setTest(int test) { _test = test; }
private:
int _test;
};
bool operator<(const node& n1, const node& n2)
{
return n1.getTest() < n2.getTest();
}
int main()
{
std::map<node,bool> foo;
node n;
n.setTest(25);
foo[n] = true;
return 0;
}
Here's how to read the error messages:
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/bits/stl_function.h: In member function ‘bool std::less<_Tp>::operator()(const _Tp&, const _Tp&) const [with _Tp = node]’:
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/bits/stl_tree.h:1141: instantiated from ‘std::pair<typename std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::iterator, bool> std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_M_insert_unique(const _Val&) [with _Key = node, _Val = std::pair<const node, bool>, _KeyOfValue = std::_Select1st<std::pair<const node, bool> >, _Compare = std::less<node>, _Alloc = std::allocator<std::pair<const node, bool> >]’
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/bits/stl_map.h:469: instantiated from ‘std::pair<typename std::_Rb_tree<_Key, std::pair<const _Key, _Tp>, std::_Select1st<std::pair<const _Key, _Tp> >, _Compare, typename _Alloc::rebind<std::pair<const _Key, _Tp> >::other>::iterator, bool> std::map<_Key, _Tp, _Compare, _Alloc>::insert(const std::pair<const _Key, _Tp>&) [with _Key = node, _Tp = bool, _Compare = std::less<node>, _Alloc = std::allocator<std::pair<const node, bool> >]’
prog.cpp:15: instantiated from here
/usr/lib/gcc/i686-pc-linux-gnu/4.3.4/include/g++-v4/bits/stl_function.h:230: error: no match for ‘operator<’ in ‘__x < __y’
First, we ignore most of the 'instantiated from' lines, because they're just talking about how the templates got expanded. The important one is the last one, referring to our source code, because it tells us where the error was triggered. Of course, we knew that anyway, so we'll skip that too. We'll also ignore the path to the library header in question, because we don't really care how the compiler stores its stuff.
stl_function.h: In member function ‘bool std::less<_Tp>::operator()(const _Tp&, const _Tp&) const [with _Tp = node]’:
stl_function.h:230: error: no match for ‘operator<’ in ‘__x < __y’
So... our code indirectly calls ‘bool std::less<_Tp>::operator()(const _Tp&, const _Tp&) const [with _Tp = node]’, or if we actually do that substitution, ‘bool std::less<node>::operator()(const node&, const node&) const’. And this is a problem because there is no match for ‘operator<’ in ‘__x < __y’.
__x and __y are variables inside the std::less implementation (you should be able to guess that much). From the name, we can guess (and if we had studied the standard library, we would know) that std::less is a template function that compares two things of the same type and returns whether or not the first is less than the second.
How does it do that? By using the operator<, of course. So that's what we need to do to fix the problem: it says the operator< isn't there for what's being compared, so we have to provide it. What's being compared? nodes, of course. So we define operator< for our class.
Why does it do that? So that we can write functions that accept a comparison-operation as an argument (either a template argument or a run-time parameter - but the former is far more common), and pass std::less. That's the reason for std::less's existence: it turns the act of comparing things into a function, and actual functions are somewhat more useful.
How is that relevant? Because, like the others said, std::map actually is passing std::less as an argument. It's actually a default argument to the std::map template that's used to compare elements. After all, part of the interface of a map is that every key is unique. How are you going to check keys for uniqueness if you can't compare them? Granted, technically you would only have to compare them for equality for that to work. But it turns out that being able to order the keys makes it possible to create a much more efficient data structure. (You would know about this if you actually took courses in university about programming and CS.)
Why wasn't there a problem with int? You should be able to guess by now: operator< already naturally works for ints. But you have to tell C++ how to do it for any user types, because you might have something else in mind.
C++11
As mentioned in Andrew Rasmussen's answer, the keys of a std::map must be comparable. However, you can also provide a custom comparison object to your map instead of defining operator< for your struct. Moreover, since C++11, you can use a lambda expression instead of defining a comparison object. As a result, you can keep your code as short as follows:
auto comp = [](const node& n1, const node& n2) { return n1.test < n2.test; };
std::map<node, bool, decltype(comp)> mymap1(comp);
Code on Ideone
I have a small program I want to execute to test something
#include <map>
#include <iostream>
using namespace std;
struct _pos{
float xi;
float xf;
bool operator<(_pos& other){
return this->xi < other.xi;
}
};
struct _val{
float f;
};
int main()
{
map<_pos,_val> m;
struct _pos k1 = {0,10};
struct _pos k2 = {10,15};
struct _val v1 = {5.5};
struct _val v2 = {12.3};
m.insert(std::pair<_pos,_val>(k1,v1));
m.insert(std::pair<_pos,_val>(k2,v2));
return 0;
}
The problem is that when I try to compile it, I get the following error
$ g++ m2.cpp -o mtest
In file included from /usr/include/c++/4.4/bits/stl_tree.h:64,
from /usr/include/c++/4.4/map:60,
from m2.cpp:1:
/usr/include/c++/4.4/bits/stl_function.h: In member function ‘bool std::less<_Tp>::operator()(const _Tp&, const _Tp&) const [with _Tp = _pos]’:
/usr/include/c++/4.4/bits/stl_tree.h:1170: instantiated from ‘std::pair<typename std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::iterator, bool> std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_M_insert_unique(const _Val&) [with _Key = _pos, _Val = std::pair<const _pos, _val>, _KeyOfValue = std::_Select1st<std::pair<const _pos, _val> >, _Compare = std::less<_pos>, _Alloc = std::allocator<std::pair<const _pos, _val> >]’
/usr/include/c++/4.4/bits/stl_map.h:500: instantiated from ‘std::pair<typename std::_Rb_tree<_Key, std::pair<const _Key, _Tp>, std::_Select1st<std::pair<const _Key, _Tp> >, _Compare, typename _Alloc::rebind<std::pair<const _Key, _Tp> >::other>::iterator, bool> std::map<_Key, _Tp, _Compare, _Alloc>::insert(const std::pair<const _Key, _Tp>&) [with _Key = _pos, _Tp = _val, _Compare = std::less<_pos>, _Alloc = std::allocator<std::pair<const _pos, _val> >]’
m2.cpp:30: instantiated from here
/usr/include/c++/4.4/bits/stl_function.h:230: error: no match for ‘operator<’ in ‘__x < __y’
m2.cpp:9: note: candidates are: bool _pos::operator<(_pos&)
$
I thought that declaring the operator< on the key would solve the problem, but its still there.
What could be wrong?
Thanks in advance.
The problem is this:
bool operator<(_pos& other)
Should be this:
bool operator<(const _pos& other) const {
// ^^^^ ^^^^^
Without the first const, the right-hand side of the comparison (b in a < b) cannot be const, since without const the function may modify its argument.
Without the second const, the left-hand side of the comparison (a in a < b) cannot be const, since without const the function may modify this.
Internally, the key's of a map are always const.
It should be noted that you should prefer to use nonmember functions. That is, better is a free-function:
bool operator<(const _pos& lhs, const _pos& rhs)
{
return lhs.xi < rhs.xi;
}
In the same namespace as your class. (For our example, just underneath it.)
By the way, in C++ there is no need to prefix the declaration of a struct type variable with struct. This is perfect, and preferred:
_pos k1 = {0,10};
_pos k2 = {10,15};
_val v1 = {5.5};
_val v2 = {12.3};
(Though your type names are admittedly named in an unorthodox manner. :P)
Lastly, you should prefer the make_pair utility function for making pairs:
m.insert(std::make_pair(k1,v1));
m.insert(std::make_pair(k2,v2));
It saves you from having to write out the types for the pair, and is generally easier to read. (Especially when longer type names come along.)
Signature of the less than operator needs to be bool operator<(const _pos& other) const, otherwise map can not use this operator in const functions since this member function is declared as non-const.
I think that your definition of operator< is wrong - the right hand side (argument in this case) should be marked const and it should be a const member function, e.g.
bool operator<(const _pos& other) const{
return this->xi < other.xi;
}