Does MISRA C++ rule 7-1-1 affect references? - c++

Rule 7-1-1 (Required) A variable which is not modified shall be const qualified
If a variable does not need to be modified, then it shall be
declared with const qualification so that it cannot be modified. A
non-parametric variable will then require its initialization at the
point of declaration. Also, future maintenance cannot accidentally
modify the value.
void b ( int32_t * );
int32_t f ( int32_t * p1, // Non-compliant
int32_t * const p2, // Compliant
int32_t * const p3 ) // Compliant
{
*p1 = 10;
*p2 = 10;
b( p3 );
int32_t i = 0; // Non-compliant
return i;
}
The example included in the standard is focused on pointers. The rule requires all the pointers meeting the conditions to be const, e.g. int * const. If I understand it correctly, it does not require pointers and references to point to const objects, e.g. const int * or const int &. In fact, it is covered by another rule (but only for parameters!):
Rule 7-1-2 (Required) A pointer or reference parameter in a function shall be declared as pointer to const or reference to const if the corresponding object is not modified
So, does the rule 7-1-1 apply to references at all? A reference cannot be re-bound after it has been created, so it should be treated like a const pointer. Therefore all references should automatically comply with the rule 7-1-1.
EDIT (based on comments by Lightness Races in Orbit, Richard Critten & Peter and my experiments): Or does the rule apply to the type of referenced object in the case of references? I mean const int & vs. int & similarly to const int vs. int?
I am asking because my MISRA C++ checker keeps reporting violations on references… Example of its behavior:
class A
{
int property;
public:
A(int param) : property(param) {} // violation: should be: const int param
int get_property() const { return property; }
void set_property(int param) { property = param; } // violation: should be: const int param
};
class ConstA
{
const int property;
public:
ConstA(int param) : property(param) {} // violation: should be: const int param
int get_property() const { return property; }
// setter not allowed
};
void example1()
{
const A const_obj_A(1);
A nonconst_obj_A(2);
ConstA nonconst_obj_constA(3); // OK: used to create a non-const reference
const A& const_ref_A = nonconst_obj_A;
A& nonconst_ref_A = nonconst_obj_A; // OK: setter called
nonconst_ref_A.set_property(4);
ConstA& const_ref_constA = nonconst_obj_constA; // violation: no modification
// In fact, it seems to be impossible to make
// a non-violating ConstA& declaration.
// The only chance is to make another non-const reference
// but the last declaration in the chain will still violate.
}
void example2()
{
const A const_obj_A(1);
A nonconst_obj_A(2);
ConstA nonconst_obj_constA(3); // violation: used only in const reference
const A& const_ref_A = nonconst_obj_A;
A& nonconst_ref_A = nonconst_obj_A; // violation: no modification using the ref.
const ConstA& const_ref_constA = nonconst_obj_constA;
}

No, 7-1-1 does not apply to references.
A declaration of the form int32_t & const p2 is nonsense.
The only meaningful const qualification of a refeence is of the form const int32_t &p2 or the equivalent int32_t const &p2. Then need for these forms is covered completely in 7-1-2.
Misra 7-1-1 doesn't need to apply to such references because the philosophy of Misra is to specify constraints that the language standard does not, not to restate constraints already specified in the language (and enforced by compilers). Misra 7-1-1 requires a variable (say, of type int32_t) to be declared const if it will not be modified - such as i in the quoted example. In standard C++, it is not possible to create a non-const reference to that variable - unless a type conversion (aka "cast") is used to remove the constness. Misra rule 5-2-5 requires that such casts shall not be used.

There is significant semantic difference between C++ pointer and reference: reference is NOT an object (variable).
It's just object alias. So by strict formal meaning 7-1-1 shall not be applied to references.

Related

Shouldn't "const references" be actually called "references to const"?

I came up with this feeling when playing with constexpr references. But the issue itself is not related to constexpr, it is merely revealed by it.
We know that there are "pointers to const" and there are "const pointers". BTW, since the latter are much less used than the former, their name is often actually used to reference the former. Anyway, there is no this distinction for references, since they are not rebindable. Thus we only have "const references" and not "references to const". This is the common terminology and it is used in the standard as well. But it does not seem right to me.
Let's take a look at some well-known examples, commented with this common terminology:
int i = 0; // Value
int* pi = &i; // Pointer to i
const int* cpi = &i; // Pointer to const i
int const* cpi = &i; // Pointer to const i (east-const style)
int* const pci = &i; // Const pointer to i
int& ri = i; // Reference to i
const int& rci = i; // Const reference to i
int const& rci = i; // Const reference to i (east-const style)
Now let's take a closer look on several lines from the above:
const int* cpi = &i; // Pointer to const i
const int& rci = i; // Const reference to i
const int* declares "pointer to const", while const int& declares "const reference". Same order in the declaration, different order in the meaning. There is a clear inconsistency here. But if we call it "reference to const" inconsistency is gone. Same order on both sides.
The tension intensifies with the introduction of constexpr:
constexpr int& cri = i; // Constexpr reference to i
constexpr const int& crci = i; // Constexpr const reference to i
"Constexpr const" reference? Huh? The documentation clearly says:
A constexpr specifier used in an object declaration implies const.
A reference is an object. So why would we need const when it is already constepxr?.. Yet we need it, if i is const. Looks frustrating.
But the frustration is no more with help of "reference to const". Let's call crci "constexpr reference to const i". Now it is clear that const is applied to the referenced object, while constexpr is applied to the reference itself, stating the fact it can be used in constexpr context. And also that it is const, since all references are const. Makes perfect sense. To make it more consistent with the pointers, we might even have used the following hypothetical syntax:
const int& constexpr crci = i; // Constexpr reference to const i
const int* const pci = &i; // Const pointer to const i (perfect consistency)
But that's not how C++ works, for better or worse.
This concept of "reference to const" really helped me to understand "constexpr const reference" thing.
What do you think? Does the concept of "reference to const" sound convincing to you?
Yes, the proper name for const int & is a "reference to const int".
It's often called a "const reference to int" (probably because it sounds better to some), but technically that's a misnomer.
Even though they are not rebindable, references themselves are never const (i.e. std::is_const_v always equals to false for a reference).
constexpr const int& crci
"Constexpr const" reference? Huh? ... Let's call crci "constexpr reference to const i"
That's correct, crci is a "constexpr reference to const i".
To make it more consistent with the pointers, we might even have used the following hypothetical syntax:
const int& constexpr crci = i; // Constexpr reference to const i
const int* const pci = &i; // Const pointer to const i (perfect consistency)
To be honest, I don't see how this proposal improves consistency. int *constexpr pci; is not allowed for pointers, so why should it be allowed for references?
Things seem to be reasonably consistent to begin with:
constexpr const int *a; // constexpr (and const) pointer to const int
constexpr const int &a; // constexpr (but not const) reference to const int
Yes, adding constexpr doesn't make a reference itself const, but I don't think it needs to be fixed. (What would be the difference between a const refenrece and a non-const reference?)
Note that constexpr is not a part of a type, but it's a property of a variable. (e.g. constexpr int x; declares x with the type const int, and not some hypothetical constexpr int).
Since volatile tends to be also be relevant, the standard often uses "reference to a cv-(un)qualified" in normative text to cover both qualifiers, which conforms to your preference.
With quick search, besides the code example that you quoted, the standard appears to use (non-) const reference in following places, all in the standard library spec:
[container.node.observers]
key_type& key() const;
Returns: A non-const reference to ...
[rand.req.adapt]
... The expression a.base() shall be valid and shall return a const reference ...
[futures.shared.future
— (19.1) shared_future::get() returns a const reference to ...
Arguably, the wording could be improved by using reference to const.
Outside of the standard, "references to const" is used. But so is const reference, which is sometimes chosen possibly due to terseness.
In fact, pointers to const are also colloquially often called const pointers even though that meaning is ambiguous, which easily leads to confusion. In case of references, there is no ambiguity to those who know that references cannot be top level qualified. Beginners won't know that, so the use of "reference to const" is important in introductory material in my opinion. But so is perhaps explanation that const reference is used to mean the same thing.

Is modifying non-mutable member of non-const object in const method Undefined Behaviour?

dcl.type.cv provides an interesting example:
For another example,
struct X {
mutable int i;
int j;
};
struct Y {
X x;
Y();
};
const Y y;
y.x.i++; // well-formed: mutable member can be modified
y.x.j++; // ill-formed: const-qualified member modified
Y* p = const_cast<Y*>(&y); // cast away const-ness of y
p->x.i = 99; // well-formed: mutable member can be modified
p->x.j = 99; // undefined: modifies a const member
which indicates that, via const_cast, one may modify mutable members of a const qualified object, while you can't do that with non-mutable members.
To my understanding, this is because of the original constness of y itself. What would happen if we got rid of the mutable keyword, the const qualifier fot y, but modified the fields in a const method?
Example below:
#include <vector>
struct foo {
std::vector<int> vec{};
void bar() const {
auto& raw_ref = const_cast<std::vector<int>&>(vec);
raw_ref.push_back(0); // ok?
auto* raw_this = const_cast<foo*>(this);
raw_this->vec.push_back(0); // ok?
}
};
int main() {
foo f{};
f.bar();
}
Does it exhibit Undefined Behaviour? I would think that it does not, since we're modifying an originally non-const, but in a const context.
Additionally, notice that I provided two ways of modifying the vec. One with non-const reference and one with non-const pointer to this (which was originally const in this constext due to foo::bar being a const method). Do they differ in any particular way, given the question's context? I would assume that both are okay here.
Disclaimer: I am aware of mutable keyword, but this desing (not only being flawed) is simply an example. One could assume that the author of the code wanted to prohibit every single way of modifying vec except for push_backs.
Your quoted paragraph actually spelled out exactly what is undefined [dcl.type.cv]
Except that any class member declared mutable can be modified, any attempt to modify a const object during its lifetime results in undefined behavior.
A const reference/pointer to a non-const object doesn't make that object a const object, all your accesses are well-formed.

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;
}
};

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)

constant references with typedef and templates in c++

I heard the temporary objects can only be assigned to constant references.
But this code gives error
#include <iostream.h>
template<class t>
t const& check(){
return t(); //return a temporary object
}
int main(int argc, char** argv){
const int &resCheck = check<int>(); /* fine */
typedef int& ref;
const ref error = check<int>(); / *error */
return 0;
}
The error that is get is invalid initialization of reference of type 'int&' from expression of type 'const int'
This:
typedef int& ref;
const ref error;
Doesn't do what you think it does. Consider instead:
typedef int* pointer;
typedef const pointer const_pointer;
The type of const_pointer is int* const, not const int *. That is, when you say const T you're saying "make a type where T is immutable"; so in the previous example, the pointer (not the pointee) is made immutable.
References cannot be made const or volatile. This:
int& const x;
is meaningless, so adding cv-qualifiers to references has no effect.
Therefore, error has the type int&. You cannot assign a const int& to it.
There are other problems in your code. For example, this is certainly wrong:
template<class t>
t const& check()
{
return t(); //return a temporary object
}
What you're doing here is returning a reference to a temporary object which ends its lifetime when the function returns. That is, you get undefined behavior if you use it because there is no object at the referand. This is no better than:
template<class t>
t const& check()
{
T x = T();
return x; // return a local...bang you're dead
}
A better test would be:
template<class T>
T check()
{
return T();
}
The return value of the function is a temporary, so you can still test that you can indeed bind temporaries to constant references.
Your code gives error because the const qualifier in const ref error is just ignored because 8.3.2/1 says
Cv-qualified references are ill-formed except when the cv-qualifiers are introduced through the use of a typedef (7.1.3) or of a template type argument(14.3), in which case the cv-qualifiers are ignored.`
So error has type int& not const int& .
It's a very common mistake for English speaking people, because of the way the English grammar works.
I consider it extremely unfortunate that the C++ syntax would allow both:
const int // immutable int
int const // immutable int
to have the same meaning.
It doesn't make it easier, really, and isn't composable since:
const int* // mutable pointer to immutable int
int* const // immutable pointer to mutable int
certainly do NOT have the same meaning.
And this, unfortunately for you, what kicks in here, as #GMan explains.
If you wish to avoid this kind of error in the future, take the habit of qualifying your types (const and volatile) on their right, then you'll be able to treat a typedef as simple text replacement.
To maintain consistency with the Right Left Rule, I prefer to use 'cv' qualifiers like so.
int const x = 2; // x is a const int (by applying Right Left rule)
int const *p = &x; // p is a pinter to const int
In your example, I would write const ref error = check<int>(); like so
ref const error = check<int>(); // parsed as error is a const reference to an integer
As #Prasoon Saurav pointed out, cv qualifiers are ignored when introduced through typedef because as #GMan also says, that cv qualified references are ill-formed.
Therefore the declaration is effectively as below, which of course is an error.
int &error = check<int>();
Check out this for more information.
This is compiled:
typedef const int& ref;
ref error = check<int>();
VC++ compiler gives some explanation of your error: qualifier applied to reference type; ignored. Reference type must be declared as constant, const cannot be applied later.