Serge Rogatch
Serge Rogatch

Reputation: 15100

C++ pointer to const pointer

I'm still confusing where to place const in pointers with more than one indirection. Can someone clarify?

E.g. right now I need a pointer to const pointer, meaning such a variable int **ppTargets that I can assign int *pTargets variable to it, like:

int foo(int **ppTargets) {
    int *pTargets = /* calculate here */;
    *ppTargets = pTargets;
    return 37; // just e.g.
}

The above code lacks const. So in foo I want pTargets to point to constant memory and be unassignable after initialization (so that one cannot write e.g. pTargets++), that would be int const *const pTargets = /* assigned once */. Next I want to declare ppTargets that ppTargets itself can be assigned, but then *ppTargets can only be read.

In the other words, in the caller code I want:

int const* pTargets;
foo(&pTargets);

I tried to declare foo as follows, but get an error you cannot assign to a variable that is const:

int foo(int *const *const ppTargets)

Upvotes: 3

Views: 2361

Answers (7)

John Bollinger
John Bollinger

Reputation: 181459

So I want pTargets to point to constant memory and be const itself, that would be int const *const pTargets = /* assigned once */. Next I want to declare ppTargets that ppTargets itself can be assigned, but then *ppTargets can only be read.

Unfortunately, that does not make sense. Your example code assigns to *ppTargets, as indeed appears to be the primary objective of function foo(). If *ppTargets can be assigned once, then it can be assigned again.

It is unclear why you want foo()'s local pTargets to be const, as opposed to just not modifying it, but you may assign a const value to an object of the corresponding non-const-qualifed type. Thus, what you're actually looking for might be

int foo(int const **ppTargets) {
    int const * const pTargets = /* calculate here */;
    *ppTargets = pTargets;
    return 37; // just e.g.
}

And that seems to be consistent with your intended usage:

In the other words, in the calling code I want:

int const* pTargets;
foo(&pTargets);

For any type T, the type of a pointer to a T can be spelled T *. In particular, the type of this &pTargets is int const ** (look familiar?), and that's the appropriate type for a function parameter through which the function should be able to set the value of the caller's pTargets.

And again, calling foo() to have it to set the value of the caller's pTargets seems to be exactly the point. If foo() were supposed to be prevented from doing that, then the ideal approach would be to pass pTargets itself (by value), instead of passing its address and wrangling const qualifiers.

Upvotes: 1

Flaviu
Flaviu

Reputation: 1039

The basic formalization for indirection is in my view

(read-only|read-write) <memory zone> * (read-only|read-write) <pointer>

where <pointer> is itself a memory zone. For double indirection, the expression becomes

(read-only|read-write) <memory zone> * (read-only|read-write) <memory zone/pointer-level2> * (read-only|read-write) <pointer-level1>

What makes things more difficult to understand is the possibility of placing qualifiers (eg. read-only) before OR after <memory zone> on the left size of * symbol. On the right side of * symbol, the qualifier(s) can be placed only before <pointer>.

In C++ read-only means const and read-write is the implicit qualifier.

Thus we can have:

  • char* p read-write pointer to read-write char memory zone
  • const char* p read-write pointer to read-only char memory zone
  • char* const p read-only pointer to read-write char memory zone
  • const char* const p read-only pointer to read-only char memory zone

Then we can move const after the basic type resulting equivalent declarations:

  • char const* p read-write pointer to read-only char memory zone
  • char const* const p read-only pointer to read-only char memory zone

Pointer conversion is allowed having equal qualifier or less restrictive qualifier in source pointer for each level of indirection comparing to destination pointer.

As result the following cases are valid:

int foo(const int* const* const p);

{// equal leftmost qualifier
const int* p = nullptr;

const int** p1 = &p; // 2nd and 3rd qualifiers are less restrictive
foo(p1);

const int* const* p2 = &p; // 2nd qualifier is equal, 3rd one (implicit read-write) is less restrictive
foo(p2);

const int* const* const p3 = &p; // 2nd and 3rd qualifiers are equal
foo(p3);
}

{// less restrictive leftmost qualifier of p
int* p = nullptr;

int** p1 = &p; // 2nd and 3rd qualifiers are less restrictive
foo(p1); 

int* const* p2 = &p; // 2nd qualifier is equal, 3rd one (implicit read-write) is less restrictive
foo(p2);

int* const* const p3 = &p; // 2nd and 3rd qualifiers are equal
foo(p3);
}

In your case the leftmost qualifier of the pointer passed as argument (&pTargets) is not equal or less restrictive than the leftmost qualifier of the pointer from foo function.

Upvotes: 0

Serge Rogatch
Serge Rogatch

Reputation: 15100

After the inputs from the others, especially the Clockwise/Spiral rule by @Mahesh , as well as some debate, I understood how to read and write such things easily.

We should look at what can and what cannot be modified. So consider the declaration without consts: int **ppTargets. We want that ppTargets cannot be modified itself, while *pTargets can be modified, while **pTargets cannot be modified.

Then, apply these observations right to left: int const * * const ppTargets.

The rightmost const says that ppTargets++ is not possible.

Then absence of const in the middle says that (*ppTargets)=pTargets is possible.

Then another, leftmost const says that (**ppTargets)++ is not possible.

Upvotes: 0

eerorika
eerorika

Reputation: 238431

So I want pTargets to point to constant memory and be const itself

Next I want to declare ppTargets that ppTargets itself can be assigned, but then *ppTargets can only be read.

For clarity, let int const * be Ptr and let int const * const (i.e. Ptr const) be CPtr.

Just like you correctly wrote int const *const pTargets (i.e. CPtr) when you wanted a const pointer to const int, so too when you want a non-const pointer to const pointer to const int (i.e. the type of &pTargets i.e. CPtr*), you need int const *const * ppTargets. Note that a Ptr* will implicitly convert to CPtr*.

Your attempted int *const *const ppTargets would be a const pointer to const pointer to non-const int. Since the type is a const pointer, it cannot be assigned which contradicts with your requirement.


More generally, a simple rule of thumb is to read C pointer type declarations from right to left, and constness applies to the left of the keyword (except when it is the leftmost token in which case it applies to the right).


Now that we've found a type that meets your stated requirements, let me bring your attention to your implementation of foo which does *ppTargets = pTargets. This contradicts with the requirement of "*ppTargets can only be read".

Upvotes: 0

John Burger
John Burger

Reputation: 3672

I always read C/C++ definitions from the right-most variable name leftwards.

So:

  • const char *p;

    p is a pointer to a char that is const

    So p can be modified, but *p can’t.

  • const char * * const p = &a;

    p is a const pointer to a pointer to a char that is const.

    So p cannot be modified (hence I initialised it); *p can; but **p can’t.

[EDIT - added arrays for completeness]

  • const char * * const p[4] = { &a, &b, &c, &d };

    p is a 4-element array of const pointers to...

Upvotes: 5

dbush
dbush

Reputation: 225007

Since pTargets is a const int *, its address is a const int **, which is the type you want for the function parameter:

int foo(const int **ppTargets)
{
    int *pTargets = malloc(sizeof(int)*4);
    pTargets[0] = 1;
    pTargets[1] = 2;
    pTargets[2] = 3;
    pTargets[3] = 4;
    *ppTargets = pTargets;
    return 37;
}

int main()
{
    int const *pTargets;
    foo(&pTargets);
    return 0;
}

EDIT:

If the variable to set is defined as int const * const pTargets;, the only way to set it is when it is initialized. You can then do this instead:

const int *foo2()
{
    int *pTargets = malloc(sizeof(int)*4);
    pTargets[0] = 1;
    pTargets[1] = 2;
    pTargets[2] = 3;
    pTargets[3] = 4;
    return pTargets;
}

int main()
{
    int const * const pTargets = foo2();
    return 0;
}

Upvotes: 1

zmbq
zmbq

Reputation: 39039

What you're looking for is int const * const * ppTarget. No, wait, you're looking for int const ** const ppTarget. No no, it's int * const * const * ppTarget.

Chances are one of them is correct (I'm betting the first one). However, you don't want people reading your code to guess what it is you mean. It's just too confusing. C++ can do that to you.

What you should do, is use typedefs to make sure people who read the code understand what you want.

typedef const int *CINT_PTR;
CINT_PTR pTarget = ....;
CINT_PTR *ppTarget = &pTarget;

Upvotes: 4

Related Questions