2018-12-30

6: Constant Method in C++: Half of the Explanations Are Wrong

<The previous article in this series | The table of contents of this series | The next article in this series>

The definition of constant method is NOT "a method that does not change the state of the object". The mix-up there is the cause of some confusions.

Topics


About: C++

The table of contents of this article


Starting Context



Target Context


  • The reader will understand what 'constant method' really is and how to circumvent an unreasonable restriction.

Orientation


Hypothesizer 7
This code does not compile, with an error, "binding reference of type ‘int&’ to ‘const int’ discards qualifiers".

@C++ Source Code
			class Test1 {
				private:
					int i_integer1;
				public:
					int & constantMethod1 () const;
			};

			int & Test1::constantMethod1 () const {
				return i_integer1;
			}

Huh? . . . Why? . . . I have been taught (by some tutorials) that 'constant method' is 'method that does not change the state of the object', and definitely, that method conforms to the criterion . . .

That error message states that "i_integer1" in 'Test1::constantMethod1' is a constant and the non-constant reference return cannot refer to the constant.

Well . . . , there are 2 issues there: 1) that statement seems odd and 2) the reality of 'constant method' does not fit the description, "method that does not change the state of the object".

Anyway, what am I supposed to do?

Some answers in a Q & A site claim that the return type should be a constant reference ('int const &', in this case). . . . Being told so, it has to be a non-constant reference for me: the contents of 'i_integer1' is going to be changed afterward.

The answers claim that if the return was a non-constant reference, the state of the Test1 object could be changed afterward. . . . So what? . . . That is exactly what I want, and that does not contradict the constant method's not changing the state of the object at all, does it? . . . I just want to ensure that the method itself does not change the state of the object; the object's being changed afterward is just fine for me.

It has turned out that an explanation like "'constant method' is 'method that does not change the state of the object'" is inappropriate. I mean, that explanation is not wrong as a description of an aspect of 'constant method' (certainly, 'constant method' does not change the state of the object), but is inappropriate as an answer to "what is 'constant method'?" because that explanation does not represent the essence of 'constant method'.

Certainly, I see also many appropriate explanations in the world, but about half (that is not an exact statistics) of the prevalent explanations seem to be basically equivalent to the inappropriate explanation.

Although I had seen both types of explanations, I had jumped to an inappropriate one, because that represented what I wanted: in fact, I wanted (and still want) a method does not change the state of the object, but unfortunately, 'constant method' is not that.


Main Body


1: What 'Constant Method' Really Is


Hypothesizer 7
In fact, 'constant method' is 'method that can be safely called on any constant object' (pay attention to the term, "object"), not 'method that does not change the state of the object', as definition.

At least, that is an explanation that really represents how 'constant method' behaves.

Only based on that correct understanding, we can understand why the above code is not allowed: returning the non-constant reference to the member datum is not safe when the object is constant.


2: Unfortunately, C++ Has No Such Thing as 'Method That Does Not Change the State of the Object'


Hypothesizer 7
Hmm . . ., I do not absolutely deny the usability of 'method that can be safely called on any constant object': as changing a constant object can lead to an undefined behavior, they want to preclude such a possibility, right?.

However, in most cases, what I want is 'method that does not change the state of the object'.

Unfortunately, C++ has no such a thing.


3: The Problem Is, C++ Requires Any Method to Be Constant When the Method Is Called on Any Constant Expression That Does Not Represent Any Constant Object


Hypothesizer 7
As 'constant method' is 'method that can be safely called on any constant object', it is quite reasonable that C++ requires any method to be constant when the method is called on any constant object. However, C++ requires any method to be constant also when the method is called on any constant expression that does not represent any constant object, which is quite unreasonable.

If somebody does not understand the difference, this code will clarify that.

@C++ Source Code
			class Test1 {
				private:
					int i_integer1;
				public:
				    // This has to be, unreasonably, a constant method because this is called on the constant expression argument in 'Test2::constantMethod2'; the return type has to a constant reference because the definition of 'constant method' requires so.
					int const & constantMethod1 () const;
			};
            
			int const & Test1::constantMethod1 () const {
				return i_integer1;
			}
			
			class Test2 {
				public:
				    // The return type has to be a constant reference because 'a_test1.constantMethod1 ()' returns a constant reference.
					int const & constantMethod2 (Test1 const & a_test1) const;
			};
			
			int const & Test2::constantMethod2 (Test1 const & a_test1) const {
				return a_test1.constantMethod1 ();
			}
			
int main (int a_argumentsNumber, char const * a_arguments []) {
    Test1 l_test1;
    Test2 const l_test2;
    int & l_integer1 = l_test2.constantMethod2 (l_test1); // This is not allowed, of course . . .
    l_integer1 = 2;
}

'constant expression' means that the represented datum (an object, in this case) cannot be changed through the expression (it is fine that the datum is changed otherwise), and 'a_test1' in 'Test2::constantMethod2' is exactly a constant expression that represents a non-constant object ('l_test1' in 'main' is non-constant).

It is absolutely fine that the object represented by 'a_test1' is changed outside of 'Test2::constantMethod2', but C++, unreasonably, decides that the object represented by 'a_test1' must be a constant object, falsely.

That sloppy decision requires 'Test1::constantMethod1' to be a constant method, which requires the return type of it to be a constant reference, which requires the return type of 'Test2::constantMethod2' to be a constant reference, which breaks my intention of 'l_integer1' to be a non-constant reference . . .


4: More Generally, the Treatment of 'Constantness' by the C++ Standard Is Sloppy


Hypothesizer 7
In fact, more generally, the treatment of 'constantness' by the C++ standard is sloppy: the C++ standard does not distinguish between 'constantness of datum' and 'constantness of expression'.

The claim by the error message cited in 'Orientation' seems odd because the constantness of "i_integer1" in 'Test1::constantMethod1' is a constantness of expression (I know that the expression is constant because 'this' is constant in any constant instance method), but any reference is bound to a datum, not to any expression, and the datum to be bound to is non-constant.


5: 'const_cast' Seems Desirable in More Cases than Some People Claim


Hypothesizer 7
Although some people vehemently claim that 'const_cast' should not be used, at least except non-constant-safe existing libraries necessitate it, it seems the best option in some cases.

I mean, the C++ standard is dissatisfactory and unreasonable, and compensating the dissatisfactoriness and the unreasonableness where they appear seems better than enduring them and devising some roundabout end runs.

In the above code, assuming to accept the official definition of 'constant method', the unreasonableness appears in that 'Test1::constantMethod1' has to be constant while 'a_test1' does not represent any constant object; so, a way to compensate the unreasonableness will be to eliminate the constantness of 'Test1::constantMethod1' and to use 'const_cast' on 'a_test1' in order to enable the call of 'Test1::constantMethod1', like this.

@C++ Source Code
			class Test1 {
				private:
					int i_integer1;
				public:
					int & constantMethod1 ();
			};
            
			int & Test1::constantMethod1 () {
				return i_integer1;
			}
			
			class Test2 {
				public:
					int & constantMethod2 (Test1 const & a_test1) const;
			};
			
			int & Test2::constantMethod2 (Test1 const & a_test1) const {
				return (const_cast <Test1 &> (a_test1)).constantMethod1 ();
			}
			
int main (int a_argumentsNumber, char const * a_arguments []) {
    Test1 l_test1;
    Test2 const l_test2;
    int & l_integer1 = l_test2.constantMethod2 (l_test1);
    l_integer1 = 2;
}

On the other hand, assuming to refuse the official definition of 'constant method' and adopt an original definition of 'constant method' as 'a method that does not change the status of the object', the dissatisfactoriness appears in that 'Test1::constantMethod1' cannot return the non-constant reference to 'i_integer1'; so, a way to compensate the dissatisfactoriness will be to use 'const_cast' on "i_integer1" in 'Test1::constantMethod1' to eliminate the constantness of "i_integer1" and return the non-constant reference to 'i_integer1', like this.

@C++ Source Code
			class Test1 {
				private:
					int i_integer1;
				public:
					int & constantMethod1 () const;
			};
            
			int & Test1::constantMethod1 () const {
				return const_cast <int &> (i_integer1);
			}
			
			class Test2 {
				public:
					int & constantMethod2 (Test1 const & a_test1) const;
			};
			
			int & Test2::constantMethod2 (Test1 const & a_test1) const {
				return a_test1.constantMethod1 ();
			}
			
int main (int a_argumentsNumber, char const * a_arguments []) {
    Test1 l_test1;
    Test2 const l_test2;
    int & l_integer1 = l_test2.constantMethod2 (l_test1);
    l_integer1 = 2;
}

Anyway, 'const_cast' is used because the C++ standard is sloppy.


6: Safety Over Reasonableness?


Hypothesizer 7
Pushing the constantness of the datum onto the constantness of any expression is not reasonable: such an intrusion is a prejudice and a bigotry.

But is it safer? . . . On the supposition that it is technically difficult to appropriately enforce 'constantness of expression', maybe so, unless one gets disgusted with the unreasonablenesses of the C++ constantness feature and decides to ditch the feature all together.

However, since when has C++ become safe? In my opinion, it has always been intrinsically unsafe; it has always been a programming language that requires the programmer to know what he or she is doing and be careful.

I mean, if a measure is reasonable and safe, of course, it is fine, but trying to be safe in sacrifice of reasonableness is objectionable to me, in general, and especially seems out of place, in a programming language in which the programmer can do unsafe things anyway.

I think that the C++ standard is heading in an odd direction.


7: The Conclusion and Beyond


Hypothesizer 7
Now, I seem to understand what 'constant method' really is: 'method that can be safely called on any constant object', not 'method that does not change the state of the object'.

However, I really want 'method that does not change the state of the object', and 'constant method' is required to be used also on any constant expression, unreasonably.

So, in order to compensate the dissatisfactoriness and the unreasonablenesses of the C++ standard, 'const_cast' seems to have some suitable places to be used in, however vehemently some people censure using it.

As there are some other unsatisfactory explanations on C++, I will try to make more reasonable explanations in future articles.


References


<The previous article in this series | The table of contents of this series | The next article in this series>