Why can a const member function modify a static data member? - c++

In the following C++ program, modifying a static data member from a const function is working fine:
class A
{
public:
static int a; // static data member
void set() const
{
a = 10;
}
};
But modifying a non-static data member from a const function does not work:
class A
{
public:
int a; // non-static data member
void set() const
{
a = 10;
}
};
Why can a const member function modify a static data member?

It's the rule, that's all. And for good reason.
The const qualifier on a member function means that you cannot modify non-mutable non-static class member variables.
By way of offering some rationalisation, the this pointer in a const qualified member function is a const type, and this is inherently related to an instance of a class. static members are not related to a class instance. You don't need an instance to modify a static member: you can do it, in your case, by writing A::a = 10;.
So, in your first case, think of a = 10; as shorthand for A::a = 10; and in the second case, think of it as shorthand for this->a = 10;, which is not compilable since the type of this is const A*.

According to the C++ Standard (9.2.3.2 Static data members)
1 A static data member is not part of the subobjects of a class...
And (9.2.2.1 The this pointer)
1 In the body of a non-static (9.2.1) member function, the keyword
this is a prvalue expression whose value is the address of the object
for which the function is called. The type of this in a member
function of a class X is X*. If the member function is declared
const, the type of this is const X*,...
And at last (9.2.2 Non-static member functions)
3 ... if name lookup (3.4) resolves the name in the id-expression to a
non-static non-type member of some class C, and if either the
id-expression is potentially evaluated or C is X or a base class of X,
the id-expression is transformed into a class member access expression
(5.2.5) using (*this) (9.2.2.1) as the postfix-expression to the
left of the . operator.
Thus in this class definition
class A
{
public:
static int a;
void set() const
{
a = 10;
}
};
the static data member a is not a subobject of an object of the class type and the pointer this is not used to access the static data member. So any member function, non-static constant or non-constant, or a static member function can change the data member because it is not a constant.
In this class definition
class A
{
public:
int a;
void set() const
{
a = 10;
}
};
the non-static data member a is an subobject of an object of the class type. To access it in a member function there is used either a member access syntax of this syntax is implied. You may not use a constant pointer this to modify the data member. And the pointer this is indeed has type const A * within the function set because the function is declared with the qualifier const. If the function had no the qualifier in this case the data member could be changed.

The thing is, that if a member function of a class A is const, then the type of this is const X*, and thereby prevents non-static data members from being altered (cf, for example, C++ standard):
9.3.2 The this pointer [class.this]
In the body of a non-static (9.3) member function, the keyword this is a prvalue expression whose
value is the address of the object for which the function is called.
The type of this in a member function of a class X is X*. If the
member function is declared const, the type of this is const X*, ...
If a is a non-static data member, then a=10 is the same as this->a = 10, which is not allowed if the type of this is const A* and a has not been declared as mutable. Thus, since void set() const makes the type of this being const A*, this access is not allowed.
If a is a static data member, in contrast, then a=10 does not involve this at all; and as long as static int a by itself has not been declared as const, statement a=10 is allowed.

The const qualifier on a member function means that you cannot modify non-mutable, non-static class data members.

Related

std::add_pointer implementation for non-static member functions

This question is a follow-up of A question regarding the implementation of std::add_pointer
Under std::add_pointer
there is the following reference:
Otherwise (if T is a cv- or ref-qualified function type), provides the
member typedef type which is the type T.
Based on reading Non-static member functions: const-, volatile-, and ref-qualified member functions, my understanding is that a for a non-static member function with given cvand/or ref qualification,
a) the cv qualification of the function applies to the this pointer as well, within the scope of the function
b) the ref qualification of the function does not apply to the this pointer within the scope of the function
Given this, why is it that std::add_pointer cannot provide the member typedef type T* in the case of a non-static member function with cv or ref qualification?
Per [dcl.ptr]/4:
[ Note: Forming a pointer to reference type is ill-formed; see
[dcl.ref]. Forming a function pointer type is ill-formed if the
function type has cv-qualifiers or a ref-qualifier; see
[dcl.fct]. Since the address of a bit-field cannot be taken, a
pointer can never point to a bit-field.  — end
note ]
The pointer-to-cv-qualified-function type you are imagining is actually nonexistent. Therefore, std::add_pointer cannot produce such a type :)
A non-static member function type cannot be formed. Such a thing does not exist.
struct T {
int func() const;
};
func does not have a type. You cannot ever use it as an expression on its own.
using mf = int (T::*)() const;
mf myfunc = &T::func;
mf is a pointer to member function type. It is not a function type; pointers to non-static members (including member functions) are data, not functions.
A cv or ref qualification on a member function does not qalify the function type, which does not exist, but the type of the implicit "this" parameter.
The paragraph you quote only applies to non-member or static member functions.

[[maybe_unused]] applied to static data members

The draft standard states about [[maybe_unused]] in 10.6.6 item 2
"The attribute may be applied to the declaration of a class, a typedef-name, a variable, a non-static data member, a function, an enumeration, or an enumerator."
Is there any reason to exclude static data members from this? i.e.
struct Foo {
[[maybe_unused]] static inline int foo = 0;
};
I ask as I have a static data member whose type has a non trivial constructor that does useful stuff but is otherwise unused.
[basic]/6 says that any object declaration constitutes a variable. “non-static data member” appears in the list alongside “variable” because a non-static data member of reference type is not a variable.

this in unevaluated context in static member functions

Why this is not allowed in unevaluated context in static member functions?
struct A
{
void f() {}
static void callback(void * self) // passed to C function
{
static_cast< decltype(this) >(self)->f();
}
};
This code gives an error:
error: 'this' is unavailable for static member functions
static_cast< decltype(this) >(self)->f();
^~~~
decltype(this) there is needed for brevity (sometimes it is much more shorter, then VeryVeryLongClassName *), another advantage is the fact that intention is more clear.
What Standard says about using this in unevaluated contexts in static member functions?
I don't see how it matters that this appears within an unevaluated context, you've referred to something that doesn't exist in a static member function, so how is the compiler supposed to deduce the type of this within this context?
As a corollary, the type of this in a non-static member function is dependent on the cv-qualifier of said member function, decltype(this) would yield T const* if the member function were const, and T * if it weren't. Thus, the type is dependent on the context of the expression. In your example, the context has no this pointer.
To alleviate the pain of having to name the class you could add an alias for it.
class VeryVeryLongClassName
{
using self = VeryVeryLongClassName;
};
The current draft of the Standard has some conflicting information.
If a declaration declares a member function or member function template of a class X, the expression this is a prvalue of type "pointer to cv-qualifier-seq X" wherever X is the current class between the optional cv-qualifier-seq and the end of the function-definition, member-declarator, or declarator.
It shall not appear within the declaration of either a static member function or an explicit object member function of the current class (although its type and value category are defined within such member functions as they are within an implicit object member function).
First it prohibits the expression this in a static member function, but then it says that the type and value category of this are defined within static member functions (and it does so in a way which resolves the question of cv qualification). Which seems useless if there is no way to refer to said this to obtain its type.
A potential resolution would be for the rule to change from "shall not appear" to "shall not be evaluated", which would immediately permit it to appear in unevaluated context such as decltype and sizeof.

C++11 decltype of member

Why can't I do this:
class Foo {
void fn();
using fn_t = decltype(fn); //call to non-static member function without an object argument
};
But I can do
class Foo {
static void fn();
using fn_t = decltype(fn);
};
This SO post claims:
Within unevaluated operands (operands of decltype, sizeof, noexcept, ...) you can name nonstatic data members also outside of member functions
fn is a valid id-expression denoting a non-static member function. §5.1.1 [expr.prim.general]/p13 (footnote omitted):
An id-expression that denotes a non-static data member or non-static
member function of a class can only be used:
as part of a class member access (5.2.5) in which the object expression refers to the member’s class or a class derived from that
class, or
to form a pointer to member (5.3.1), or
if that id-expression denotes a non-static data member and it appears in an unevaluated operand.
§7.1.6.2 [dcl.type.simple]/p4:
The operand of the decltype specifier is an unevaluated operand
(Clause 5).
Since decltype is not one of the few contexts in which an id-expression denoting a non-static member function may be used, the program is ill-formed.

What all is not permitted with const member functions?

class A{
private:
int a;
public:
A() {a = 4;}
const int& random1() const {return a; }
//int& random2() const {return a; }
const int* random3() const {return &a;}
//int* random4() const {return &a;}
};
int main(){
A objA;
cout<<objA.random1()<<"\n";
cout<<*objA.random3()<<"\n";
}
random2() and random4() are not permitted as defined above. I somehow knew this all along but never came across it while writing my own code, until today.
What all except these two cases is not permitted in const member functions?
Any reference to C++ standard text will also be helpful. Thanks!
First understand that const T* is a pointer to some T that cannot be changed. The second thing to remember is all members are actually accessed via this->.
So (§9.3.1):
A nonstatic member function may be declared const, volatile, or const volatile. These cvqualifiers affect the type of the this pointer (9.3.2).
And what it does (§9.3.2):
In the body of a nonstatic (9.3) member function, the keyword this is a non-lvalue expression whose value is the address of the object for which the function is called. The type of this in a member function of a class X is X*. If the member function is declared const, the type of this is const X*, if the member function is declared volatile, the type of this is volatile X*, and if the member function is declared const volatile, the type of this is const volatile X*.
A const on a function makes the this pointer const T*.
This is why those examples fail: In the int& variant, a is accessed as this->a, this is const T*, so a is a const int. And const int cannot be implicitly converted to int&. Same with the other function.
In other words, when a function is const it smacks a const on everything in the class, and you can't implicitly cast the const away.
Const member functions can't call non-const member function, even if they don't change any member data. Sometimes you need to provide both const and non-const versions of the same function, because this pointer is implicitly passed to member functions and plays a role in overload resolution.