Why is const redundant when using const functions C++? - c++

So all modifiers that I have come across in C++ have came prior to the name of the function. Why is const different? Why must it both precede the name and come prior to the function block?
const int getSize() const;
or
const int getSize const {
...
}

These are 2 different usages of the const keyword.
In this case:
const int getSize() {
the function is returning an int that is const, i.e. the return value cannot be modified. This is not very useful, since the const in the return value is going to be ignored (compilers will warn about this). const in the return type is only useful when returning a const*, or a const&.
In this case:
int getSize() const {
this is a const-qualified member function, i.e. this member function can be called on const objects. Also, this guarantees that the object will not be modified, even if it's non-const.
Of course, you can use both of these together:
const int getSize() const {
which is a const-qualified member function that returns a const int.

The const before the function is applied to the return type of the function. The const after is only for member functions and means that the member function is callable on a const object.
As a note returning a const int does not make sense, many compilers will warn that the const gets discarded.

Related

Are some usages of const only really useful when returning or passing things by reference? Or do they have subtle uses I'm not seeing

I've read about the various places to put const. Some usages seem clearly useful to me. Others however evade me. It would be really helpful if someone could confirm or correct my understanding as I explain my mental model of things.
These are the same. I'm not sure I understand why this would ever be useful, though. Does it perhaps allow one to initialize const int variables with a function, which in turn allows some compiler optimizations?
const int foo();
int const foo();
These are the same. The returned pointer cannot be used (via dereferencing) to change the values pointed to.
const int * foo();
int const * foo();
This means the returned pointer itself cannot be changed. But, why would it matter if the caller essentially decides to ignore the returned pointer and set it to something else? Is this only really useful if the pointer is returned by reference?
int * const foo();
These are the same. It means you can only pass in const ints, which allows the compiler to optimize things.
int foo(const int foo);
int foo(int const foo);
This means the passed-in pointer cannot be changed. I'm wondering here too, why would it matter unless the pointer is being passed in by reference?
int foo(int * const foo);
This (as a member function) guarantees that the function won't change the state of the object. Also, if the object itself is declared const, then it will only be able to call such functions.
int foo(int foo) const;
const int as return type is pointless, because in an expression the type of a non-class prvalue result of a function call will be stripped of its top-level const anyway. There is no distinction between a const and non-const prvalue of a non-class type. So the type of foo() is just int, no matter whether it is declared to return const int or int.
It would be different if the return type was a const qualified class type, e.g. const std::string foo(). In that case the const would disallow calling non-const-qualified member functions directly on the result of foo(), e.g. foo().resize(42). Still, this is a very rarely used distinction. And as noted in the comments, under certain conditions it can prevent move operations. E.g. in the above if we have a std::vector<std::string> v;, then v.push_back(foo()) will cause a copy, rather than a move, of the returned string into the vector.
However, the const qualifier is part of the return type in the function type and therefore it is technically possible to differentiate a function declared with const return type from one without it. The type of int foo(int foo) is int(int), but the type of const int foo(int foo) is const int(int). (However overloading based on return type is not possible for non-template functions anyway. The return type is not part of the function signature.)
correct
Same as 1. The type of foo() is simply int*.
The top-level const in the function parameter does not affect the type or signature of the function (in contrast to 1. where it does affect the type). So int foo(const int foo); and int foo(int foo); declare the same function and both have type int(int). Top-level const also doesn't affect how a variable can be initialized, so it doesn't make sense to say "you can only pass in const int". There are no const-qualified prvalues of type int anyway and if int foo can be initialized from some expression, then so can const int foo. The const has no implication on initialization or overload resolution. However const can be used like this in a function definition to tell the compiler and yourself that this parameter is not intended to be modified in the definition.
Same as 4.
This is the correct idea, although in the details it is not strictly true. Rather the const is only relevant to overload resolution (behaving as if the implicit object parameter was a const reference) and the type of this (which will be a pointer to const). It is still possible to mutate members declared as mutable or to use const_cast to mutate members. It is also not relevant whether the object itself is const, only whether the glvalue through which the member function is called is.

Mutable Lvalue Reference of Parameter Passed by const Reference

I just found an interesting case: I passed an object by const reference and still I was able to modify its member (which happens to be an lvalue reference). Following is an example:
#include <iostream>
#include <string>
struct PersonRef
{
PersonRef(std::string& name_) : _name(name_) {}
std::string& _name; // <==== IMPORTANT: it is a reference
};
void testRef(const PersonRef& pr) {
std::string& name = pr._name; // binding to lvalue reference? How does it even compile?
name = "changed!";
}
int main() {
std::string name = "trivial_name";
PersonRef pr{name};
std::cout << pr._name << "\n"; // prints: trivial_name
testRef(pr);
std::cout << pr._name << "\n"; // prints: changed!
}
I used to think that if the parameter is passed by const ref, then object is immutable but this doesn't appear to be the case here. Could someone please explain this? Thanks!
Note that in const member function, the data member _name itself will be considered as const. This doesn't make any difference on _name because it's a reference, which can't be const-qualified. (In a sence the reference is always const, you can't modify the reference itself, the reference can't be rebound to other object after initialization.)
On the other hand, the referenced object won't be become const, so it's still possible to be modified, _name won't become const std::string& (reference to const).
Similar thing happens on pointer members; in const member function they become const pointers, but not pointers to const; you still could modify the pointed objects if they're non-consts from the beginning.
Consider a different example.
Let's say you have struct A {int *x;};.
When accessed through const A &ref, x would have type int *const - a const pointer to (non-const) int.
As you can see, const is not added recursively to the pointer. It's only added at the top level.
Back to your code. Strictly speaking, const PersonRef& pr is not a const reference. It's a non-const reference to const PersonRef.
(In theory, a const reference would be written as PersonRef &const pr. But since references are not rebindable to begin with, adding const wouldn't do anything, and thus isn't allowed. So technically references are never const, even though you can't rebind them.)
The compiler can't add const-ness to to std::string& _name, since references can't be const. And it doesn't add const-ness recursively to the referenced type, simply because that's how the language works.
If you don't want this behavior, make std::string& _name private and add a pair of const and non-const accesors:
class PersonRef
{
std::string &_name;
public:
PersonRef(std::string& name_) : _name(name_) {}
std::string &name() {return _name;}
const std::string &name() const {return _name;}
};

C++ const correctness with reference members

I have a fstream & member in a class, on which I'm calling the seekg function in a const function of the class, and yet the code compiles. I checked, and the seekg is not declared const (nor should it be), so how is this happening?
This is my code:
class Test {
fstream &f;
public:
Test(fstream &f_): f(f_) {}
int fid() const {
f.seekg(5);
return 0;
}
};
It turns out the const does not apply to members that are pointers or references, as stated here.
The best explanation I've seen of this is here where it states that inside const member functions, this is a const T *, where T is the class.
In your example that means that all the const modifier on fid() does is to change this from a Test * to a const Test * inside the function. When you write f., this is accessed as this->f. which is of type fstream & const. The reference is const but what it refers to is not, so calling a function that modifies it causes no problems.
The rule is defined in [expr.ref]/4:
If E2 is declared to have type “reference to T”, then E1.E2 is an lvalue; the type of E1.E2 is T. [...]
In practice you should consider a reference to T, as a const pointer to T with automatic dereferencement. Internaly this is what are reference. And inside the standard, all rules that applies to reference (see [basic.life] for example) are those rules that would apply to a const pointer:
class Test {
fstream * const f;
public:
Test(fstream &f_): f(&f_) {}
int fid() const {
f->seekg(5);
return 0;
}
};

Int& to const int -static vs dynamic-

class A
{
public:
A(int i = 25) {x = i;y=new int[i];for(int j=0;j<i;j++) y[j]=j;}
int& f() const {return x;}
int& operator[](int i) const {return y[i];}
private:
int x,*y;
};
int main()
{
A a(15);
cout << a[5];
cout << a.f();
return 0;
}
When I'm trying to compile the code it says
"Invalid initialization of reference of type int& from expression of type const int"
regarding f() function.
I understand it,since it returns a non-const reference to something declared as const by the function. But shouldn't it behave the same with the [] overloading?
It also returns a non-const reference to something that the function declared as const,but it shows no error there.
What's the difference?
It is telling you that you can't return a non-const lvalue reference to a data member from a const member function. You need
const int& f() const {return x;}
You may decide to provide a non-const overload if needed:
int& f() {return x;}
As for operator[], it does not return a reference to a data member. You cannot modify x or y via operator[], so it is really a const member function. You may decide to disallow modification to the data pointed at by y, and this would make sense if your class models an array. But it isn't strictly necessary and the compiler has no reason to enforce it.
const int& operator[](int i) const {return y[i];}
int& operator[](int i) {return y[i];}
The issue is here:
int& f() const {return x;}
Your function is marked const, so it can be only invoked by const instances. However, you return a non-const reference, therefore if valid you can use it an modify const instances. The compiler is not happy with this. Hence f() should return const int& instead.
On the other hand, int& operator[](int) const compiles, since you return a reference to the data the pointer member y points to, but you cannot modify the pointer itself. In other words, on a const instance, the pointer y is const, i.e. int * const y, but not the data. Therefore bitwise const-ness is preserved, but of course logical const-ness is not, however the compiler only cares about bit-wise const-ness.
To enforce logical const correctness, one option is to write 2 versions of your operator[]:
const int& operator[](int i) const {return y[i];}
and
int& operator[](int i) {return y[i];}
Note that the second version should be marked non-const, as otherwise you'd try to overload two functions that differ only by their return type. If you want to avoid code duplication in the non-const version, you can make use of the const version via a const_cast, like
int& operator[](int i)
{
return const_cast<int&>(const_cast<const A&>(*this)[i]); // use the const version
}
EDIT
There is a proposal to introduce a const-propagating wrapper for pointer-like data members, propagate_const, which in effect will also make the data pointed to const, see
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4057.pdf
In C++ language constness of a class implies immediate constness of all its members (except for mutable ones). However, constness of the class does not propagate to data pointed by pointer members or referenced by reference members. That pointed/referenced data is not part of the class and its constness is not affected by constness of the class.
In your example, declaring a member function as const makes it treat members x and y as const. However, the data pointed by y does not become const, e.g. neither *y nor y[i] are const.
Propagation of constness from the enclosing class to the pointed/referenced data is actually a part of user intent. It is something you need or don't need depending on what you are trying to implement. The language leaves it completely up to you (or to a library-level solution).

c++ constant function declaration variants?

Are all of the below declarations the same? If so, what is the standard way to declare a constant function?
const SparseMatrix transpose();
SparseMatrix transpose() const;
const SparseMatrix transpose() const;
The const on the left of the function name means the object that is returned cannot be modified. The const on the right means the method is apart of a class and does not modify any of its data members. Unless or course any of its data members are declared with the mutable keyword, in which case modification thereof is permitted despite a const guard.
The placement of the const keyword is unimportant when the return type of the function is of non-pointer type:
T const f(); // same as const T f();
However, note that the placement of the const keyword matters when using a pointer as the return type. For example:
const T* f();
This method returns a pointer to a const T. That is, what it points to is immutable. So you cannot do an assignment through a dereference of the returned pointer:
T* x = f();
*x = y; // error: assignment of read-only location '*(const T*)x'
When const is placed on the immediate right of the return type (that is a pointer), it means the pointer is const and cannot be changed.
T* const f();
int main()
{
T* x const;
x = f(); // error: assignment of read-only variable 'x'
}
Furthermore, if we have const on both sides of a pointer return type, and have const denoting "no modification of class members", then it's read as the following:
const T* const f() const;
A const member function named f that returns a const pointer to a const T
The first one will return a SparseMatrix that is const and cant be changed.
The second one declares a function that returns a SparseMatrix and assures the function will not change any class variables (assuming it is a member function, otherwise it wouldnt make sense with this deceleration) except for mutable members.
The final one does both.
1) return a const value
2) const function, no member changes inside it
3) 1)+2)