Type of 'This' Pointer

Type of 'this' pointer

The type of this pointer is either ClassName * or const ClassName *, depending on whether it is inspected inside a non-const or const method of the class ClassName. Pointer this is not an lvalue.

class ClassName {
void foo() {
// here `this` has `ClassName *` type
}

void bar() const {
// here `this` has `const ClassName *` type
}
};

The observation you mentioned above is misleading. Pointer this is not an lvalue, which means that it cannot possibly have ClassName * const type, i.e. it cannot possible have a const to the right of the *. Non-lvalues of pointer type cannot be const or non-const. There's simply no such concept in C++ language. What you observed must be an internal quirk of the specific compiler. Formally, it is incorrect.

Here are the relevant quotes from the language specification (emphasis mine)

9.3.2 The this pointer

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 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*.
[ Note: thus in
a const member function, the object for which the function is called
is accessed through a const access path. —end note ]


It is worth nothing that back in the C++98/C++03 times several compilers used an internal implementational trick: they interpreted their this pointers as constant pointers, e.g. ClassName *const in a non-constant method of class ClassName. This apparently helped them to ensure non-modifiablity of this. GCC and MSVC are known to have used the technique. It was a harmless trick, since at language level this was not an lvalue and its constness was undetectable. That extra const would generally reveal itself only in diagnostic messages issued by the compiler.

However, with the advent of rvalue references in C++11 it became possible to detect this extra const on the type of this. For example, the following code is valid in C++11

struct S
{
void foo() { S *&&r = this; }
};

Yet it will typically fail to compile in implementations that still use the aforementioned trick. GCC has since abandoned the technique. MSVC++ still uses it (as of VS2017), which prevents the above perfectly valid code from compiling in MSVC++.

How can I get the type of the variable a pointer points to?

So it seems that there is no real solution to this other than using virtual functions.

In C++, why is mentioning the type of the pointer necessary?

If you declare b as auto, the compiler will use type inference and determine the type itself. But if you use a specific type, it has to be the correct one.

the type of this* in C++

For beginners, this is often depicted as a constant pointer.

However, this is actually a prvalue (pure rvalue) of pointer type. You can't assign anything to prvalues of fundamental type, which implies the "const-ness" of this.

The exact type of this depends on the cv-qualification of the method. A rule of thumb is that the cv-qualification is simply prepended to the usual pointer type - i.e., if a method of Class is marked const, then the type is const Class*.

if the compiler can compile it fine whether there is a 'const' or
not,why does it matter?

If (and only if) the pointee type of this is const, you can't modify the members of the class.

Class const* ptr; // ptr->data is also const, not modifiable through this pointer

Class* ptr; // ptr->data isn't const - can be modified.

The const-qualifier on methods allows you to distinguish between methods for const objects and methods for non-const ones, which is often a necessity.

Why do you have to specify a type for pointers?

You don't have to specify a type for pointers. You can use void* everywhere, which would force you to insert an explicit type cast every single time you read something from the address pointed by the pointer, or write something to that address, or simply increment/decrement or otherwise manipulate the value of the pointer.

But people decided a long time ago that they were tired of this way of programming, and preferred typed pointers that

  • do not require casts
  • do not require always having to know the size of the pointed type (which is an issue that gets even more complicated when proper memory alignment has to be taken into consideration)
  • prevent you from accidentally accessing the wrong data type or advancing the pointer by the wrong number of bytes.

And yes, indeed, all data pointers, no matter what their type, occupy the same amount of memory, which is usually 4 bytes on 32-bit systems, and 8 bytes on 64-bit systems. The type of a data pointer has nothing to do with the amount of memory occupied by the pointer, and that's because no type information is stored with the pointer; the pointer type is only useful to humans and to the compiler, not to the machine.

Incompatible pointer type warning with pointer-to-pointer types when passing char** to void** function parameter

C lets any pointer be implicitly cast to void* as an explicit exception. Note, that void and char are not compatible types. Thus void*, char*, void** and char** are not compatible as well. That is why compiler emits a warning.

To bypass this issue change the function signature to use void*:

void secure_free(void* ptr, size_t length_in_bytes) {
void **p_p_data = (void**)ptr;
...
}

To add protection that the argument is a pointer to a pointer one could use a macro:

#define secure_free(x,s) ((void)sizeof **(x), secure_free((x), (s)))
  • Expression **(x) will not compile is x were not a pointer to a pointer.
  • sizeof prevent evaluation of x in **(x) to avoid side effects
  • (void) silence the compiler about complaining on unused value
  • comma operator (X,Y), return only value of the Y, which is return value of secure_free(...)
  • using the same name for macro as for function allows to expand secure_free as macro only if it used as a function. This allows to use secure_free as a pointer to a function

Extra note. In the code

    memset(*p_p_data, 0, length_in_bytes);
free(*p_p_data);

The compiler will likely optimize out memset(). I suggest casting to volatile void * to force the compiler to generate clearing code.

Edit

Additionally, one may have clear the content with a loop because memset discards volatile qualifier.

C getting type of a pointer

You can perform sort-of-overloading on types using _Generic macro if you are using a C11 compliant compiler, but the type has to be stored separately, be it a void* or an union with some sort of tag.

The closest to what you want is sth like this:

#include <stdio.h>
#include <assert.h>

struct TaggedUnion
{
void* contents;
/* union {int* i;char* c;} contents; */

enum Type {CHAR_TYPE, INT_TYPE} type;
};

struct TaggedUnion storeI(int* x)
{
struct TaggedUnion ret = {.contents =x, .type=INT_TYPE};
return ret;
}

struct TaggedUnion storeC(char* x)
{
struct TaggedUnion ret = {.contents =x, .type=CHAR_TYPE};
return ret;
}

#define storeTagged(x) _Generic((x),\
int*: storeI,\
char*: storeC)(x)

int* getInt(const struct TaggedUnion x)
{
return x.type == INT_TYPE ? x.contents : NULL;
}

char* getChar(const struct TaggedUnion x)
{
return x.type == CHAR_TYPE ? x.contents : NULL;
}

void fi(int* arg)
{
printf("i\n");
}

void fc(char* arg)
{
printf("c\n");
}


#define ff(x) _Generic((x),\
int*: fi,\
char*: fc)(x)

int main(int argc, const char* argv[])
{
printf("entry\n");
int* i;
char* c;

ff(i);
ff(c);

struct TaggedUnion ti = storeTagged(i);
struct TaggedUnion tc = storeTagged(c);

assert(ti.type == INT_TYPE);
assert(tc.type == CHAR_TYPE);

return 0;
}

https://godbolt.org/z/5dcrc84fo

Can a pointer to an incomplete type be incomplete?

An array of unknown size is incomplete:

An array type of unknown size is an incomplete type. It is completed, for an identifier of that type, by specifying the size in a later declaration (with internal or external linkage).

The type int (*)[] however is not incomplete: It's a pointer of an array of int of unknown size.

And a pointer has a well known size:

printf ("Size %d\n", sizeof(int (*)[]));

6.2.5/23: A type has known constant size if the type is not incomplete and is not a variable length array type.

Furthermore you can even dereference it, thanks to the array semantics:

typedef int (*T)[];
...
int a[10];
for (int i=0; i<10; i++) a[i]=i;
T p=a;
for (int i=0; i<10; i++) printf ("%d ",(*p)[i]);
printf ("\n");

Edit

In addition, a pointer is always a complete type. It's written black on white in 6.2.5/20:

A pointer type may be derived from a function type or an object type,
called the referenced type. A pointer type describes an object whose
value provides a reference to an entity of the referenced type. A
pointer type derived from the referenced type T is sometimes called
‘‘pointer to T’’. The construction of a pointer type from a referenced
type is called ‘‘pointer type derivation’’. A pointer type is a
complete object type.

pointer name does not name a type error

These:

front = NULL;
rear = NULL;

are assignment statements. And this:

Node *front = NULL, *rear = NULL;

is a declaration (definition, initialization) statement using in-class initializers.

Assignment statements are not allowed to appear in the class declaration body, whereas the initialization statements are.



Related Topics



Leave a reply



Submit