Reputation: 15100
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
Reputation: 181459
So I want
pTargets
to point to constant memory and beconst
itself, that would beint const *const pTargets = /* assigned once */
. Next I want to declareppTargets
thatppTargets
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
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 zoneconst char* p
read-write pointer to read-only char
memory zonechar* const p
read-only pointer to read-write char
memory zoneconst char* const p
read-only pointer to read-only char
memory zoneThen we can move const
after the basic type resulting equivalent declarations:
char const* p
read-write pointer to read-only char
memory zonechar const* const p
read-only pointer to read-only char
memory zonePointer 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
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 const
s: 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
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
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
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
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 typedef
s 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