Why isn't the [] operator const for STL maps? - c++

Contrived example, for the sake of the question:
void MyClass::MyFunction( int x ) const
{
std::cout << m_map[x] << std::endl
}
This won't compile, since the [] operator is non-const.
This is unfortunate, since the [] syntax looks very clean. Instead, I have to do something like this:
void MyClass::MyFunction( int x ) const
{
MyMap iter = m_map.find(x);
std::cout << iter->second << std::endl
}
This has always bugged me. Why is the [] operator non-const?

For std::map and std::unordered_map, operator[] will insert the index value into the container if it didn't previously exist. It's a little unintuitive, but that's the way it is.
Since it must be allowed to fail and insert a default value, the operator can't be used on a const instance of the container.
http://en.cppreference.com/w/cpp/container/map/operator_at

Now that with C++11 you can have a cleaner version by using at()
void MyClass::MyFunction( int x ) const
{
std::cout << m_map.at(x) << std::endl;
}

Note for new readers.
The original question was about STL containers (not specifically about the std::map)
It should be noted there is a const version of operator [] on most containers.
It is just that std::map and std::set do not have a const version and this is a result of the underlying structure that implements them.
From std::vector
reference operator[](size_type n)
const_reference operator[](size_type n) const
Also for your second example you should check for a failure to find the element.
void MyClass::MyFunction( int x ) const
{
MyMap iter = m_map.find(x);
if (iter != m_map.end())
{
std::cout << iter->second << std::endl
}
}

Since operator[] might insert a new element into the container, it can't possibly be a const member function. Note that the definition of operator[] is extremely simple: m[k] is equivalent to (*((m.insert(value_type(k, data_type()))).first)).second. Strictly speaking, this member function is unnecessary: it exists only for convenience

An index operator should only be const for a read-only container (which doesn't really exist in STL per se).
Index operators aren't only used to look at values.

If you declare your std::map member variable to be mutable
mutable std::map<...> m_map;
you can use the non-const member functions of std::map within your const member functions.

Related

Taking built-in types by value in a structured binding declaration in a range-for for std::map

I have:
std::map<double, Foo> map;
for (auto&& [first, second] : map) { /* ... */ }
where Foo is a class declared elsewhere
There are 2 issues that I see as it stands:
1. Mapped type constness
second here is Foo& which is correct, but you can also use std::as_const to make it const Foo&
for (auto&& [first, second] : std::as_const(map)) { /* ... */ }
so this is not a problem.
2. By value copy of built-in types
first here is const double& which is inefficient for built-in types.
Is there a way to make first be taken by value?
I'm sure this wasn't overlooked.
first here is const double& which is inefficient for built-in types.
Are you thinking that the compiler will create a pointer, and a pointer dereference, to implement the reference?
That's not really true. Your compiler is smart.
This may be a consideration for function arguments because those can't be "optimised" in the same way across object boundaries, but that's not what you have here.
The following code:
int a = 42;
const int& b = a;
std::cout << b << '\n';
is the same program as:
int a = 42;
std::cout << a << '\n';
and it's going to compile to the same instructions.
Your for-loop example isn't much more complex than that.
second here is Foo& which is correct, but you can also use std::as_const to make it const Foo&
for (auto&& [first, second] : std::as_const(map)) { /* ... */ }
so this is not a problem.
So what's not a problem? There is no problem here.
tl;dr: Don't try to fix problems that don't exist.

What is the difference between at and operator[] in a const method? [duplicate]

Contrived example, for the sake of the question:
void MyClass::MyFunction( int x ) const
{
std::cout << m_map[x] << std::endl
}
This won't compile, since the [] operator is non-const.
This is unfortunate, since the [] syntax looks very clean. Instead, I have to do something like this:
void MyClass::MyFunction( int x ) const
{
MyMap iter = m_map.find(x);
std::cout << iter->second << std::endl
}
This has always bugged me. Why is the [] operator non-const?
For std::map and std::unordered_map, operator[] will insert the index value into the container if it didn't previously exist. It's a little unintuitive, but that's the way it is.
Since it must be allowed to fail and insert a default value, the operator can't be used on a const instance of the container.
http://en.cppreference.com/w/cpp/container/map/operator_at
Now that with C++11 you can have a cleaner version by using at()
void MyClass::MyFunction( int x ) const
{
std::cout << m_map.at(x) << std::endl;
}
Note for new readers.
The original question was about STL containers (not specifically about the std::map)
It should be noted there is a const version of operator [] on most containers.
It is just that std::map and std::set do not have a const version and this is a result of the underlying structure that implements them.
From std::vector
reference operator[](size_type n)
const_reference operator[](size_type n) const
Also for your second example you should check for a failure to find the element.
void MyClass::MyFunction( int x ) const
{
MyMap iter = m_map.find(x);
if (iter != m_map.end())
{
std::cout << iter->second << std::endl
}
}
Since operator[] might insert a new element into the container, it can't possibly be a const member function. Note that the definition of operator[] is extremely simple: m[k] is equivalent to (*((m.insert(value_type(k, data_type()))).first)).second. Strictly speaking, this member function is unnecessary: it exists only for convenience
An index operator should only be const for a read-only container (which doesn't really exist in STL per se).
Index operators aren't only used to look at values.
If you declare your std::map member variable to be mutable
mutable std::map<...> m_map;
you can use the non-const member functions of std::map within your const member functions.

std::set::find vs std::find on std::set with const

I wrote a little (working) test code but I do not understand why in the test1 function I can only pass a int* const as parameter while in the test2 function I can pass a const int*. If I pass a const int* to test1, I get a discard qualifier error.
In my research, I found that both std::find and set::find have a const version so I can't see why they behave differently. I also tried with boost::container::flat_set instead of a std::set and I got the same result.
Could someone explain me please?
class myClass
{
public:
myClass() {};
~myClass() {};
void add(int* ref)
{
this->_ref.insert(ref);
};
bool test1(int* const ref) const
{
return ( this->_ref.find(ref) != this->_ref.end() );
}
inline
bool test2(const int* ref) const
{
return ( std::find(this->_ref.begin(), this->_ref.end(), ref) != this->_ref.end() );
}
std::set<int*> _ref;
};
int main()
{
myClass test;
test.add(new int(18));
test.add(new int(35));
test.add(new int(78));
test.add(new int(156));
std::cout<<test.test1(0)<<std::endl;
std::cout<<test.test1(*test._ref.begin())<<std::endl;
std::cout<<test.test2(0)<<std::endl;
std::cout<<test.test2(*test._ref.begin())<<std::endl;
return 0;
}
set::find() gives the answer in O(logN) while std::find() gives the answer in O(N) .
Similarly, map::find() gives the answer in O(logN) while std::find() gives the answer in O(N) .
The container std::set<int*> has only homogeneous lookup, so you can only search keys by comparing them with a value of the same type: find, count, erase. Naturally, a value of type const int* does not have the same type as int*, so your test2 code attempts to convert the former to the latter, which is not an allowed conversion.
The fact that containers could only be used in a homogeneous way like that has been a shortcoming of C++ since inception, and more egregrious examples of undesired conversions are when you have a map with std::string keys and want to look up an element with a key provided as a string literal. You always have to construct the dynamic std::string object, even though std::string provide comparisons operators with string literals.
Therefore, since C++14, you can also make a set (or map) with inhomogeneous lookup by spelling it std::set<int*, std::less<>>. With such a container, the loopup functions become templates, and you can indeed compare values of different types (leaving the conversion logic to the underlying <-operator). But note that std::less<int*> is required to provide a strict weak ordering on pointers, whereas std::less<> is not, so you may end up with undefined behaviour.

Does const containers have only const iterator?

Why do const STL containers only return const_iterators?
For example both std::vector and std::list have the method begin overloaded
as:
iterator begin();
const_iterator begin() const;
const_iterator cbegin() const;
I thought I could still modify values of a const vector but not the vector itself. According to the standard library there is no difference between:
const std::vector<int>
and
const std::vector<const int>
Suppose you have
iterator begin() const;
instead of
const_iterator begin() const;
Now, think what happens when you have
const vector<Foo> v;
You will be able to do something like
*v.begin() = other_foo;
which of course shouldn't be legal if you want to preserve logical const-ness. The solution is therefore to make the return type const_iterator whenever you invoke iterators on const instances.
The situation is similar to having const classes that have pointer members. In those cases, you may modify the data the pointer points to (but not the pointer itself), so logical const-ness is not preserved. The standard library took a step forward and disallowed these kind of modifications on standard containers via const overloads that return const_iterators.
If you declare your vector as
const std::vector<int> foo;
Then the vector itself is const, meaning you cannot push_back, erase, etc. However, you can modify its elements
for (std::vector<int>::iterator it = foo.begin(); it != foo.end(); ++it)
{
int& x = *it;
x++; // This is fine!
}
When you iterate over a vector, you are enforcing that the elements of the vector are const. So you can modify the vector by adding and removing things, but you may not modify the actual elements.
std::vector<Foo> values; // Neither the vector nor its elements are const
for (std::vector<Foo>::const_iterator it = values.cbegin(), it != values.cend(); ++it)
{
Foo const& foo = *it; // I may not try to modify foo
it->CallToNonConstMethod(); // Can't do this either
}

C++ array class with function operator for assignment

I want to write my own 2d array class. I want the class to be able to assign a value to an element like this.
a(0,0) = 1
I know this must be possible because I can do it with the matrix classes from blaze and eigen. My first idea was to overload the function operator (), which already works great to access the elements. When I try to assign a value with a(0,2) = 0; I get a compiler error.
lvalue required as left operand of assingment
What operator do I have to overload that the assignment also works?
You need to build a function with this prototype:
T& operator()(std::size_t, std::size_t);
Where T is your element type. This function needs to return a reference to an element in the array. This allows you to modify the value of the element via that reference in the caller.
As well as the above, you ought to provide the function
const T& operator()(std::size_t, std::size_t) const;
for read-only element access. I've rather dogmatically insisted on std::size_t for your indexing. In reality, you might want a typedef of your own to stand in for the index type.
You need to provide two overloads - one const and another one not, returning a reference. For example:
template <typename TVal> class val {
std::map<int, TVal> values;
public:
// the distinction is between a const version ...
TVal operator()(int key) const {
auto found = values.find(key);
if (found != values.end())
return found->second;
return TVal();
}
// ... and a non-const, returning a reference
TVal& operator()(int key) {
return values[key];
}
};
and then you can use it as follows:
val<int> v;
v(1) = 41;
v(1)++;
std::cout << v(1) << std::endl; // -> 42
std::cout << v(2) << std::endl; // -> 0
Please post the compile-error, for others to see and precisely answer it.
Having said that, I am pretty sure that it is because you are not returning a reference from your operator.