2021-01-17

16: A Thorough Guide to Dealing with 'const' in C++

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

A shallow understanding like ''const' makes it constant' does not work. 'const' has different meanings.

Topics


About: C++

The table of contents of this article


Starting Context



Target Context


  • The reader will know how to deal with any 'const' in C++.

Orientation


The different meanings of 'const' are not sufficiently explained, prevalently.

The C++ standard is not always reasonable.


Main Body


1: Those Prevalent Explanations Do Not Work


Hypothesizer 7
Let me think of a class.

theBiasPlanet/coreUtilitiesTests/constantsTest2/Test1Test.hpp

@C++ Source Code
#include <string>

using namespace ::std;

namespace theBiasPlanet {
	namespace coreUtilitiesTests {
		namespace constantsTest2 {
			class Test1Test {
				private:
					static void test ();
					static void method0 (string const & a_string0, string & a_string1);
			};
		}
	}
}

theBiasPlanet/coreUtilitiesTests/constantsTest2/Test1Test.cpp

@C++ Source Code
#include "theBiasPlanet/coreUtilitiesTests/constantsTest2/Test1Test.hpp"
#include <iostream>

namespace theBiasPlanet {
	namespace coreUtilitiesTests {
		namespace constantsTest2 {
			void Test1Test::test () {
				~
			}
			
			void Test1Test::method0 (string const & a_string0, string & a_string1) {
				cout << "### 'a_string0' at the start: " << a_string0 << endl << flush;
				a_string1 += " P.S. Do not forget.";
				cout << "### 'a_string0' at the end  : " << a_string0 << endl << flush;
			}
		}
	}
}

What does that 'const' mean?

A prevalent explanation is "The datum referred to by the reference is constant.".

No, that is not true. Let me think of an implementation of 'Test1Test::test ()', like this, which is completely legitimate.

@C++ Source Code
			void Test1Test::test () {
				{
					cout << "### Revealing the sloppiness of prevalent explanations  Start" << endl << flush;
					string l_string0 {"a string"};
					string l_string1 {"another string"};
					method0 (l_string0, l_string1);
					cout << "### Revealing the sloppiness of prevalent explanations  End" << endl << flush;
				}
			}

@Output
### Revealing the sloppiness of prevalent explanations  Start
### 'a_string0' at the start: a string
### 'a_string0' at the end  : a string
### Revealing the sloppiness of prevalent explanations  End

The datum referred to by the reference is the value of 'l_string0', which is not constant at all.

Let us refrain from making such a sloppy explanation.

Another prevalent explanation is "The datum referred to by the reference is guaranteed to not change throughout the lifetime of the reference".

Final answer? . . . Well, let me change the implementation of 'Test1Test::test ()', like this, also which is completely legitimate.

@C++ Source Code
			void Test1Test::test () {
				{
					cout << "### Revealing the sloppiness of prevalent explanations  Start" << endl << flush;
					string l_string0 {"a string"};
					string l_string1 {"another string"};
					method0 (l_string0, l_string0);
					cout << "### Revealing the sloppiness of prevalent explanations  End" << endl << flush;
				}

@Output
### Revealing the sloppiness of prevalent explanations  Start
### 'a_string0' at the start: a string
### 'a_string0' at the end  : a string P.S. Do not forget.
### Revealing the sloppiness of prevalent explanations  End

It is not guaranteed to not change, at all!

If that code looks too contrived, I can legitimately change the datum from another thread.

Won't we refrain from making such sloppy explanations?

The problem of such sloppy explanations is that they impede the proper judgment of whether each 'const' should be there or not: as we make the judgment based on the understanding of the meaning of the 'const', we need that understanding to be accurate in order to make the proper judgment.


2: Should I Use 'const'?


Hypothesizer 7
In the 1st place, why should I be bothered with 'const'? Because of it, I have to suffer some annoying restrictions like 'the variable is not allowed there because it includes a 'const''. . . . Is it masochism?

No. I use 'const' because it is very beneficial for clarifying the code.

When I read some code, my concern is usually where some data are modified; 'const' specifications reliably reduce the areas I have to look at, which is huge help

So, my stance is that 'const' should be used everywhere it can be used.

And once we decide to use 'const', we usually cannot use it by halves, because use of 'const' is chained: if an object is declared to be 'const', the object's instance methods to be used have to be made 'const'; the reference arguments into which the object is to be put have to be made 'const'; and so on.


3: The Notation to Be Used


Hypothesizer 7
This notation is allowed by the C++ standard.

@C++ Source Code
					const string * const l_string0 (new const string ("a string"));

Well, the 1st "const" modifies "string" from the left, and the 2nd "const" modifies "const string *" from the right.

Such a rule does not seem to be principled, sometimes modifying from the left, and at the other times modifying from the right.

I understand the reason why the 2nd "const" cannot modify from the left like 'const const string *': if "const string" becomes 'string', it will become 'const string *', but that is ambiguous whether that is '(const string) *' or 'const (string *)'.

In fact, there is a better notation.

@C++ Source Code
					string const * const l_string0 (new string const ("a string"));

In that notation, 'const' always modifies from the right: the 1st "const" modifies "string" and the 2nd "const" modifies "string const *", so, it is '( (string const) *) const'.

That notation is also consistent with the fact that any constant method is written like this.

@C++ Source Code
					void method1 () const;

That "const" is modifying from the right, right?

I will always use the better notation.


4: The Different 'const's


Hypothesizer 7
'const' assumes different meanings based on where it is used; I have to study each case separately.

As a preparation, I create a class, 'ClassA', like this.

theBiasPlanet/coreUtilitiesTests/constantsTest2/ClassA.hpp

@C++ Source Code
#ifndef __theBiasPlanet_coreUtilitiesTests_constantsTest2_ClassA_hpp__
	#define __theBiasPlanet_coreUtilitiesTests_constantsTest2_ClassA_hpp__
	
	#include <iostream>
	#include <string>
	
	using namespace ::std;
	
	namespace theBiasPlanet {
		namespace coreUtilitiesTests {
			namespace constantsTest2 {
				class ClassA {
					private:
						string i_string {"a string"};
					public:
						string i_publicString1 {"a public string"};
						string * i_publicString2 {new string ("another public string")};
						ClassA ();
						ClassA (string const & a_string);
						virtual ~ClassA ();
						ClassA (ClassA const & a_copiedObject);
						virtual ClassA & operator = (ClassA const & a_assignedFromObject);
						virtual void append (string const & a_string);
						virtual string get () const;
						virtual string & doesNotChangeObject ();
						friend ostream & operator << (ostream & a_outputStream, ClassA const & a_classA);
				};
			}
		}
	}
#endif

theBiasPlanet/coreUtilitiesTests/constantsTest2/ClassA.cpp

@C++ Source Code
#include "theBiasPlanet/coreUtilitiesTests/constantsTest2/ClassA.hpp"

namespace theBiasPlanet {
	namespace coreUtilitiesTests {
		namespace constantsTest2 {
			ClassA::ClassA () {
			}
			
			ClassA::ClassA (string const & a_string): i_string (a_string) {
			}
			
			ClassA::~ClassA () {
				if (i_publicString2 != nullptr) {
					delete i_publicString2;
					i_publicString2 = nullptr;
				}
			}
			
			ClassA::ClassA (ClassA const & a_copiedObject): i_string (a_copiedObject.i_string) {
			}
			
			ClassA & ClassA::operator = (ClassA const & a_assignedFromObject) {
				i_string = a_assignedFromObject.i_string;
				return *this;
			}
			
			void ClassA::append (string const & a_string) {
				i_string.append (a_string);
			}
			
			string ClassA::get () const {
				return i_string;
			}
			
			string & ClassA::doesNotChangeObject () {
				return i_string;
			}
			
			ostream & operator << (ostream & a_outputStream, ClassA const & a_classA) {
				a_outputStream << a_classA.i_string;
				return a_outputStream;
			}
		}
	}
}


4-1: On Any Normal Variable


Hypothesizer 7
'const' can be used on any normal variable, like this.

@C++ Source Code
					ClassA const l_classA {string ("a string")};

That means 1) the value of the variable cannot be changed and 2) any non-'const' instance method of the object cannot be called.

To be sure, 1) means both that the variable cannot be reassigned and that the object in the variable cannot be changed.

@C++ Source Code
					l_classA = ClassA (string ("another string")); // not allowed: reassigning
					l_classA.i_publicString1.append (string (" P.S. Do not forget.")); // not allowed: changing the object

Of course, this is allowed, because the string object pointed by 'l_publicString2' is not any part of the 'l_classA' object (although the address of the string object is).

@C++ Source Code
					l_classA.i_publicString2->append (string (" P.S. Do not forget."));

Also to be sure, 2) is not included in 1), because any instance method that does not change the object but is not declared to be 'const' cannot be called.

@C++ Source Code
					l_classA.doesNotChangeObject (); // not allowed: calling a non-'const' method
					l_classA.get ();


4-2: On Any Pointer


Hypothesizer 7
'const' can be used on any pointer, like these.

@C++ Source Code
					ClassA l_classA0 {string ("a string")};
					ClassA l_classA1 {string ("another string")};
					ClassA const * const l_classA2 {&l_classA0};
					ClassA * const l_classA3 {&l_classA0};
					ClassA const * l_classA4 {&l_classA0};

Each of those "const"s on 'ClassA' (the 1st "const" in the 3rd line and the 1st "const" in the 5th line) means that the datum pointed by the variable cannot be changed via the variable.

It is important to not be fooled by inaccurate explanations like "The datum pointed by the variable is 'const'." and "The datum pointed by the variable is guaranteed to not change throughout the lifetime of the variable.".

Let me look at this, which is completely legitimate.

@C++ Source Code
					cout << "### l_classA2 before changed: " << *l_classA2 << endl << flush;
					l_classA0.append (string (" P.S. Do not forget."));
					cout << "### l_classA2 after changed: " << *l_classA2 << endl << flush;

@Output
### l_classA2 before changed: a string
### l_classA2 after changed: a string P.S. Do not forget.

The datum pointed by the variable is NOT 'const' and CAN change at any time.

What are prohibited are only like this.

@C++ Source Code
					l_classA2->i_publicString1.append (string (" P.S. Do not forget.")); // not allowed: changing the object via the variable

As can be guessed, this is allowed.

@C++ Source Code
					l_classA2->i_publicString2->append (string (" P.S. Do not forget."));

That "const" on "ClassA const *" or "ClassA *" (the 2nd "const" in the 3rd line and the 1st "const" in the 4th line) means that the pointer cannot be reassigned.

So, these are prohibited.

@C++ Source Code
					l_classA2 = &l_classA1; // not allowed: reassigning
					l_classA3 = &l_classA1; // not allowed: reassigning


4-3: On Any Reference


Hypothesizer 7
'const' can be used on any reference, like this.

@C++ Source Code
					ClassA l_classA0 {string ("a string")};
					ClassA const & l_classA1 {l_classA0};

That means that the datum referred to by the variable cannot be changed via the variable.

Again, it is important to not be fooled by inaccurate explanations like "The datum referred to by the variable is 'const'." and "The datum referred to by the variable is guaranteed to not change throughout the lifetime of the variable.".

Let me look at this, which is completely legitimate.

@C++ Source Code
					cout << "### l_classA1 before changed: " << l_classA1 << endl << flush;
					l_classA0.append (string (" P.S. Do not forget."));
					cout << "### l_classA1 after changed: " << l_classA1 << endl << flush;

@Output
### l_classA1 before changed: a string
### l_classA1 after changed: a string P.S. Do not forget.

The datum referred to by the variable is NOT 'const' and CAN change at any time.

What are prohibited are only like this.

@C++ Source Code
					l_classA1.i_publicString1.append (string (" P.S. Do not forget.")); // not allowed: changing the object via the variable

As can be guessed, this is allowed.

@C++ Source Code
					l_classA1.i_publicString2->append (string (" P.S. Do not forget."));

There is no construction like 'ClassA & const', because any reference cannot be reassigned anyway.


4-4: On Any Array


Hypothesizer 7
'const' can be used on any array, like these.

@C++ Source Code
					ClassA l_classA {string ("a string")};
					ClassA const l_array1 [1] {string ("another string")};
					ClassA const * const l_array2 [1] {&l_classA};
					ClassA * const l_array3 [1] {&l_classA};
					ClassA const * l_array4 [1] {&l_classA};

Those "const"s are about each element, and the effects on the element accord to the corresponding part of the above descriptions, depending whether the element is a normal variable or a pointer (cannot be any reference).

Why cannot the element be a reference? That is because while any array means a consecutive area of the memory, such a reference would mean a location of an existing datum that exists outside the array area (which this article will clarify).

But any reference to any whole array can be defined, like this.

@C++ Source Code
					ClassA l_array0 [1] {string ("a string")};
					ClassA const (& l_array6) [1] {l_array0};

This is not allowed.

@C++ Source Code
					l_array6 [0].i_publicString1.append (string (" P.S. Do not forget.")); // not allowed: changing the element object via the variable

Likewise, any pointer to any whole array can be defined, like this.

@C++ Source Code
					ClassA const (* const l_array5) [1] {&l_array0};

That 2nd "const" is about the array pointer, and its effects are no different from those on any pointer.

There is no construction like 'ClassA l_array0 [1] const', because any array cannot be reassigned anyway.


4-5: On a Typical Container


Hypothesizer 7
'const' on any container is not particularly really special, but let me look at some cases on a typical container, '::std::map', because they may be somewhat confusing.

'const' can be used on 'map', like these (let me think of only normal variables, because the effects in the other cases will be able to be guessed easily from this consideration).

@C++ Source Code
					map <string const, ClassA const> const l_map00 { {"a key string", string ("a value string")}};
					map <string, ClassA const> const l_map0 { {"a key string", string ("a value string")}};
					map <string, ClassA> const l_map1 { {"a key string", string ("a value string")}};
					map <string, ClassA const> l_map2 { {"a key string", string ("a value string")}};

Well, that "const" on the key type is allowed (at least in GCC 9.3.0), but is it meaningful?

In fact, the keys are read-only anyway: these are not allowed.

@C++ Source Code
					l_map0.find (string ("a key string"))->first = string ("another key string"); // not allowed anyway: reassigning the key
					l_map0.find (string ("a key string"))->first.append (string (" P.S. Do not forget.")); // not allowed anyway: changing the key object

I would rather refrain from meaninglessly putting 'const' on the keys.

'map <string, ClassA> const' and 'map <string, ClassA const>' are different in that the latter allows reassignments of the variable.

@C++ Source Code
					l_map1 = map <string, ClassA> { {"another key string", string ("another value string")}}; // not allowed: reassigning
					l_map2 = map <string, ClassA const> { {"another key string", string ("another value string")}};

'map <string, ClassA const> const' does not seem to be meaningful compared with 'map <string, ClassA> const', because the 'ClassA' values cannot be changed anyway.

@C++ Source Code
					l_map1.at (string ("a key string")) = ClassA (string ("another value string")); // not allowed: reassigning the value
					(l_map1.at (string ("a key string"))).i_publicString1.append (string (" P.S. Do not forget.")); // not allowed: changing the value

That "ClassA const" part can be 'ClassA const * const', 'ClassA * const', 'ClassA const *', or 'ClassA const &', and the meanings are no different from those explained in some of the above subsections.


4-6: On Any Function Return


Hypothesizer 7
'const' can be used for any function return, like these.

@C++ Source Code
						virtual ClassA const method2 ();
						virtual ClassA const * const method3 ();
						virtual ClassA * const method4 ();
						virtual ClassA const * method5 ();
						virtual ClassA const & method6 ();

Each of them means that any function call expression assumes the restrictions explained in the above subsections.

These examples will clarify what I mean.

@C++ Source Code
					ClassB l_classB;
					
					l_classB.method2 ().i_publicString1.append (string (" P.S. Do not forget.")); // not allowed: changing the object
					l_classB.method3 ()->i_publicString1.append (string (" P.S. Do not forget.")); // not allowed: changing the object via the function call expression
					l_classB.method4 ()->i_publicString1.append (string (" P.S. Do not forget."));
					l_classB.method5 ()->i_publicString1.append (string (" P.S. Do not forget.")); // not allowed: changing the object via the function call expression
					l_classB.method6 ().i_publicString1.append (string (" P.S. Do not forget.")); // not allowed: changing the object via the function call expression

"l_classB.method2 ()" is a function call expression, for example.

This is OK.

@C++ Source Code
					ClassA l_classA = l_classB.method2 ();
					l_classA.i_publicString1.append (string (" P.S. Do not forget."));

That is because "l_classA" holds a copy (which is not 'const') of the returned 'const' object, not the returned 'const' object itself.


4-7: On Any Class Instance Method


Hypothesizer 7
'const' can be used on any class instance method, like this.

@C++ Source Code
						void method1 () const;

As has been elaborated in a previous article, the meaning of 'const' for any class instance method is that the method can be called also on 'const' objects, not that "the method does not change the object".

For example, this is not allowed although the method does not change the object at all.

theBiasPlanet/coreUtilitiesTests/constantsTest2/ClassA.hpp

@C++ Source Code
#ifndef __theBiasPlanet_coreUtilitiesTests_constantsTest2_ClassA_hpp__
	#define __theBiasPlanet_coreUtilitiesTests_constantsTest2_ClassA_hpp__
	
	#include <iostream>
	#include <string>
	
	using namespace ::std;
	
	namespace theBiasPlanet {
		namespace coreUtilitiesTests {
			namespace constantsTest2 {
				class ClassA {
					private:
						string i_string {"a string"};
					public:
						~
						virtual string & doesNotChangeObject () const;
						~
				};
			}
		}
	}
#endif

theBiasPlanet/coreUtilitiesTests/constantsTest2/ClassA.cpp

@C++ Source Code
#include "theBiasPlanet/coreUtilitiesTests/constantsTest2/ClassA.hpp"

namespace theBiasPlanet {
	namespace coreUtilitiesTests {
		namespace constantsTest2 {
			~
			
			string & ClassA::doesNotChangeObject () const {
				return i_string;
			}
			
			~
		}
	}
}

Inside any 'const' method, 'this' (which is a keyword, not any pointer) becomes 'const' (why that prohibits the above code will be clarified in the next section).


5: Dealing with 'const's


Hypothesizer 7
As I now understand the meanings of different 'const's, let me see how to deal with different 'const's.

For example, what function arguments can a variable that has some 'const'ness be put into? Or what function returns can a variable that has some 'const'ness be returned as?


5-1: Terminology


Hypothesizer 7
In my terminology, any and only variable whose value cannot be changed is a 'const' variable.

Is that overcommon? Well, I mean that this is not any 'const' variable.

@C++ Source Code
					ClassA const * l_classA {new ClassA (string ("a string"))};

Why? Because 'l_classA''s value (which is nothing but an address) can be changed. That "const" is not about the value of the variable.

So, not all the variables that have some 'const' specifications are 'const' variables.

I will call any variable that has some 'const' specifications but is not any 'const' variable, 'const'-related variable.


5-2: Initializing 'const' Variables


Hypothesizer 7
Any 'const' variable has to be initialized at the definition, can be initialized by an initializer of a constructor if it is a class instance field, or can be initialized by a function call if it is a function argument.

This is an example of initializing by an initializer of a constructor.

theBiasPlanet/coreUtilitiesTests/constantsTest2/ClassA.hpp

@C++ Source Code
#ifndef __theBiasPlanet_coreUtilitiesTests_constantsTest2_ClassC_hpp__
	#define __theBiasPlanet_coreUtilitiesTests_constantsTest2_ClassC_hpp__
	
	#include <string>
	
	using namespace ::std;
	
	namespace theBiasPlanet {
		namespace coreUtilitiesTests {
			namespace constantsTest2 {
				class ClassC {
					private:
						string const i_string;
					public:
						ClassC (string const & a_string);
				};
			}
		}
	}
#endif

theBiasPlanet/coreUtilitiesTests/constantsTest2/ClassC.cpp

@C++ Source Code
#include "theBiasPlanet/coreUtilitiesTests/constantsTest2/ClassC.hpp"

namespace theBiasPlanet {
	namespace coreUtilitiesTests {
		namespace constantsTest2 {
			ClassC::ClassC (string const & a_string): i_string (a_string) {
			}
		}
	}
}

Note that putting something into a function argument in a function call initializes the argument.


5-3: What Can Be Set to What Variable


Hypothesizer 7
Let me understand the ruling principles.

1st, I have to discern what is modified by each 'const' is something to be copied or not.

These are some examples of 'const's that modify what are to be copied.

@C++ Source Code
					ClassA const l_classAFrom0 {string ("a string")};
					ClassA * const l_classAFrom1 {new ClassA (string ("a string"))};
					ClassA const & l_classAFrom2 {* (new ClassA (string ("a string")))};
					
					ClassA l_classATo0 = l_classAFrom0;
					ClassA * l_classATo1 = l_classAFrom1;
					ClassA l_classATo2 = l_classAFrom2;

That 'const' about 'l_classAFrom1' is on the address, and the address is copied, right?

On the other hand, this is an example of 'const's that modify what are not to be copied.

@C++ Source Code
					ClassA const * l_classAFrom3 {new ClassA (string ("a string"))};
					
					ClassA * l_classATo3 = l_classAFrom3; // not allowed

The 'ClassA' datum is not copied, right?

The 1st principle is that any 'const' that modifies what is to be copied does not put any restriction for being set or being set into.

That is because the original cannot suffer any harm by its copy's being created, whether the copy is newly 'const' or not: they are just 2 different objects.

Only when the 'const' modifies what is not to be copied, some safety considerations kick in.

The 2nd principle is that when the 'const' modifies what is not to be copied, the 'const' cannot be stripped but can be augmented.

For example, these are not allowed.

@C++ Source Code
					ClassA const * l_classAFrom4 {new ClassA (string ("a string"))};
					ClassA const & l_classAFrom5 {* (new ClassA (string ("a string")))};
					
					ClassA * l_classATo4 = l_classAFrom4; // not allowed: 'const' is stripped
					ClassA & l_classATo5 = l_classAFrom5; // not allowed: 'const' is stripped

Well, the prohibition against stripping is not any logical necessity ('const' of that type means only that the datum cannot be changed via the variable, and changing the datum via a new non-'const' variable is not necessarily bad), but as a default behavior, it is practical (if 'const'ness could be stripped so nonchalantly, 'const' would lose its practical usefulness).

But these are allowed.

@C++ Source Code
					ClassA * l_classAFrom6 {new ClassA (string ("a string"))};
					ClassA & l_classAFrom7 {* (new ClassA (string ("a string")))};
					
					ClassA const * l_classATo6 = l_classAFrom6; // 'const' is augmented
					ClassA const & l_classATo7 = l_classAFrom7; // 'const' is augmented

Augmenting is allowed because it only increases safety and the C++ standard does not disapprove the increased safety.

That is all of the principles, narrowly speaking.

I said "narrowly speaking", because also a principle of object-oriented programming language rules there.

For example, these are not allowed.

@C++ Source Code
					map <string, ClassA> l_mapFrom0 { {"a key string", string ("a value string")}};
					map <string, ClassA *> l_mapFrom1 { {"a key string", new ClassA (string ("a value string"))}};
					
					map <string, ClassA const> * l_mapTo0 = &l_mapFrom0; // not allowed: incompatible types
					map <string, ClassA const *> * l_mapTo1 = &l_mapFrom1; // not allowed: incompatible types

Why? Those 'const's are being augmented, which should not be any problem, right? . . . Well, those prohibitions are not from any safety consideration of 'const'ness, but from a principle of object-oriented programming language: the object that is pointed or referred to by any pointer or reference has to be in an 'is-a' relationship with the type of the pointer or reference.

The point is that "const"s in "ClassA const" and "ClassA const *" are regarded as parts of the datum types.

. . . Is that reasonable? Well, as for "ClassA const", it is understandable because that "const" is an attribute of the datum itself (the datum is absolutely 'const'), but as for "ClassA const *", I do not think so: that "const" is not really any attribute of the datum, because the datum is not necessarily 'const'.

Anyway, the logic by which "map <string, ClassA>" and "map <string, ClassA *>" are not in any 'is-a' relationship with "map <string, ClassA const>" and "map <string, ClassA const *>", respectively, is the same with the logic in an article for Java array, if those "const"s are regarded as parts of the datum types.

Obviously, the situation is likewise with any template or array.


5-4: What Can Be Returned as What Return Type


Hypothesizer 7
The relation between the function return type and what is returned is the same with that of between the destination variable type and what is set, explained in the previous subsection.

For example, these are just fine, because the "const"s are on what are to be copied.

@C++ Source Code
			ClassA ClassB::method7 () {
				ClassA const l_classA {string ("a string")};
				return l_classA;
			}
			
			ClassA * ClassB::method8 () {
				ClassA * const l_classA {new ClassA (string ("a string"))};
				return l_classA;
			}
			
			ClassA ClassB::method9 () {
				ClassA const & l_classA {* (new ClassA (string ("a string")))};
				return l_classA;
			}

This is not allowed, because the "const" is on what is not to be copied and the "const" is stripped.

@C++ Source Code
			ClassA * ClassB::method10 () {
				ClassA const * l_classA {new ClassA (string ("a string"))};
				return l_classA; // not allowed: 'const' is stripped
			}

These are allowed, because the "const"s are on what are not to be copied but the "const"s are augmented.

@C++ Source Code
			ClassA const * ClassB::method5 () {
				ClassA * l_classA {new ClassA ()};
				return l_classA; // 'const' is augmented
			}
			
			ClassA const & ClassB::method6 () {
				ClassA * l_classA {new ClassA ()};
				return *l_classA; // 'const' is augmented
			}

These are not allowed, because what are returned are not in any 'is-a' relationship with the return types.

@C++ Source Code
			map <string, ClassA const> * ClassB::method11 () {
				map <string, ClassA> l_map { {"a key string", string ("a value string")}};
				return &l_map; // not allowed: incompatible types
			}
			
			map <string, ClassA const *> * ClassB::method12 () {
				map <string, ClassA *> l_map { {"a key string", new ClassA (string ("a value string"))}};
				return &l_map; // not allowed: incompatible types
			}


5-5: When the C++ Standard Is Unreasonable


Hypothesizer 7
In a previous subsection, I have raised a doubt upon the reasonableness of the C++ standard.

I will raise another doubt here.

Let me think of an example.

@C++ Source Code
			ClassA * ClassB::method10 () {
				ClassA const * l_classA {new ClassA (string ("a string"))};
				return l_classA; // not allowed: 'const' is stripped
			}

That is not allowed because that "const" on 'ClassA' is being stripped in returning.

But I am not happy about that: that "const" means that the datum cannot be changed only via the variable, and I do not intend the datum to be constant outside the function, at all. . . . Forcing that "const"ness that is only about what cannot be done via the variable that is valid only inside the function, for the return type that is about after the function exited, does not make sense to me.

As a whole, the C++ standard is sloppy about the distinctions between the different meanings of 'const's, I have to say.

By the way, one of my pet peeves on C++ is that this is not allowed.

@C++ Source Code
			void ClassB::method13 (ClassA & a_classA) {
				a_classA.append (string (" P.S. Do not forget. Do not forget."));
				cout << "### a_classA: " << a_classA << endl << flush;
			}
			
			~
					ClassB l_classB;
					l_classB.method13 (ClassA {string ("a string")}); // not allowed: a rexpression is passed into
			~

Any rexpression (so-misnomer-ly-called "rvalue") cannot be passed into the argument, but I have not heard any convincing reason for such a restriction: the passed datum lives until the function ends, and nothing is wrong with its being changed inside the function.

I am forced to do like this (a nuisance!), although I want the 'ClassA' object to be nothing but temporary.

@C++ Source Code
					ClassA l_classA {string ("a string")};
					l_classB.method13 (l_classA);

Or like this using so-misnomer-ly-called "move semantics" (this has really nothing to do with "move"!).

@C++ Source Code
			void ClassB::method14 (ClassA && a_classA) {
				a_classA.append (string (" P.S. Do not forget. Do not forget."));
				cout << "### a_classA: " << a_classA << endl << flush;
			}

I say that 'ClassA const & a_classA' is not my option because 'a_classA' has to be changed inside the method.

We need to acknowledge that the C++ standard is sometimes unreasonable, and that acknowledgment leads to the next subsection.


5-6: Stripping the 'const'ness Using 'const_cast'


Hypothesizer 7
I can strip 'const'ness using 'const_cast', like this,

@C++ Source Code
			ClassA * ClassB::method16 () {
				ClassA const * l_classA {new ClassA (string ("a string"))};
				return const_cast <ClassA *> (l_classA); // I use 'const_cast' just because the C++ standard is unreasonable.
			}

. . . Is it not un-recommendable to strip 'const'ness? Well, there may be some people who adamantly disapprove using 'const_cast', but I think, it is perfectly OK, if I am just countering some unreasonableness of the C++ standard. I am cornered to do so because the C++ standard is sloppy! . . . Of course, I have to know what I am doing, but C++ has always been nothing but a programming language in which one has to know what he or she is doing.

Unfortunately, I do not know how to counter the unreasonableness cited in the subsection, '5-3' ('const_cast' does not work for remedying it).


References


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