3

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)
8
  • 4
    use typedef to build up from your simple types to the ones you actually want to use Commented Jan 4, 2019 at 20:56
  • 2
    Clockwise/Spiral rule Commented Jan 4, 2019 at 21:14
  • You're contradicting yourself: So I want pTargets to point to constant memory and be const itself, that would be int const *const pTargets ... then In the other words, in the calling code I want: int const* pTargets; So, which is it? Is pTargets supposed to be const or not? Commented Jan 4, 2019 at 21:34
  • @eerorika, in the other words, I want to forbid assignment to pTargets and **pTargets, but not *pTargets. I don't see a contradiction yet, let me think what could be misunderstood... Commented Jan 4, 2019 at 21:36
  • @SergeRogatch I want to forbid assignment to pTargets contradicts with int const* pTargets because int const* can be assigned. Commented Jan 4, 2019 at 21:37

7 Answers 7

5

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...

Sign up to request clarification or add additional context in comments.

4 Comments

This. So the OP wants int foo(const int ** const ppTargets) (or int foo(int const ** const ppTargets), which is the same thing).
OTOH, toplevel const in parameters is effectively meaningless for copyable types.
@TonyK no, OP said "that ppTargets itself can be assigned". You cannot assign a const int ** const. And furthermore, the address of int const *const has a different type.
@eerorika: yes, reading the question again, you are right. But the OP contradicts himself, I think. So I will bow out of this one.
4

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;

2 Comments

The second one is correct, as John Burger explains.
That just makes my point stronger. It took three answers from people with 5-digit Stack Overflow reputation (two of them close to 100,000) to get this right. Don't write code that can only be understood by a group of experts after some debate.
1

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;
}

6 Comments

Now how to avoid ppTargets++ in the code of foo? Meaning that the variable itself is also const.
@SergeRogatch ppTargets is a local variable to foo, so if you change it then it isn't reflected in the calling function.
Calling this foo won't work if "pTargets [points] to constant memory and [is] const itself" like Serge states in the question. Your pTargets is non-const.
Why is pTargets declared as int const *and not const int * in main, then?
@zmbq Those are equivalent.
|
1

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.

2 Comments

This won't compile since you can't assign to *ppTargets
Yes, @dbush. I let myself be thrown off by the first part of the OP's request, which is for something that cannot actually work. I have substantially rewritten this answer to address the problem with the OP's request and present something that does work. I'm sure you won't be surprised that the function signature that works is what you presented, too.
0

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".

Comments

0

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.

Comments

0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.