0

In some legacy code I see the following:

class myClass : public CObject
{
    DECLARE_DYNAMIC(myClass)

public:
    void myClassMethod() const;
    ....
}
std::vector<const myClass*> vMyClass;
...
for (const myClass *const* Q = vMyClass.begin(); Q != vMyClass.end(); Q++)
        (*Q)->myClassMethod();

I don't understand what const is doing here. (I can't search for it, either in this forum or in Google, without the asterisks being stripped off, so searching for an explanation is useless.)

What I do know is that this code generally assumed that vector.begin() returns a pointer rather than an iterator. So I tried rewriting the for loop as:

std::vector<myClass*>::iterator Q;

for (Q = vMyClass.begin(); Q != vMyClass.end(); Q++)
    (*Q)->myClassMethod();

But I get the following error from the Visual Studio 2003 C++ compiler:

error C2678: binary '!=' : no operator found which takes a left-hand operand of type 'std::vector<_Ty>::iterator' (or there is no acceptable conversion) with [ _Ty=myClass * ]

I don't understand what's going wrong here... In another part of the code I have:

std::vector<myOtherClass> vMyOtherClass;
...
std::vector<myOtherClass>::iterator Z;

for (Z = vMyOtherClass.begin(); Z != vMyOtherClass.end(); ++Z)
    ...

this compiles cleanly. Any ideas why I get the error on the vMyClass loop or what to do about it? Any idea what const is doing?

New Information

Originally, I had copied the vector definition incorrectly. Now that it's fixed (above), I see that the iterator was missing a const. It should read:

std::vector<const myClass*>::iterator Q;

But when I made this fix in my code, the error message switched from the call to vector.end() to vector.begin():

error C2679: binary '=' : no operator found which takes a right-hand operand of type 'std::vector<_Ty>::const_iterator' (or there is no acceptable conversion) with [ _Ty=const myClass * ]

I don't get how it could work for vector.end() with operator !=, yet be wrong for vector.begin() with operator =...?

Todd Hoatson
  • 123
  • 2
  • 18
  • You seem need to use `const_iterator` instead, but better use `auto` – Slava Nov 09 '17 at 18:28
  • @Slava in VS 2003 auto will not compile. –  Nov 09 '17 at 18:30
  • @manni66 did not know, thanks – Slava Nov 09 '17 at 18:31
  • VS 2003 is really outdated. Consider using VS 2017! –  Nov 09 '17 at 18:32
  • @manni66, you're basically correct. Actually auto compiles, but it is interpreted as int. So it doesn't really work. See my comment below about our choice of VS 2003. – Todd Hoatson Nov 09 '17 at 20:40
  • @Slava, if I use const_iterator, I get a similar error. – Todd Hoatson Nov 09 '17 at 20:43
  • Without [mcve] I can only guess, and I am not going to. – Slava Nov 09 '17 at 22:53
  • _"Without Minimal, Complete, and Verifiable example..."_ @Slava, I have 167 source files & 173 header files. This is not a little school project. Would you like me to post all of them here in order to have a "complete" example? Not sure what you mean by "minimal" or "verifiable"... _"... I can only guess, and I am not going to."_ Thanks for letting us know. – Todd Hoatson Nov 09 '17 at 23:26
  • Turns out, @Slava, you were on the right track. Using const_iterator was necessary, but it was not sufficient by itself. It needed another `const: std::vector::const_iterator Q;` I don't know why this shifted the error from vector.end() to vector.begin(). Error messages can be so unhelpful... – Todd Hoatson Nov 10 '17 at 16:03

3 Answers3

1

I don't understand what const is doing here.

const applies to the thing on its left, unless nothing is there, then it applies to the thing on its right instead.

Remember that whitespace doesn't matter, so let's make your declaration a little easier to read:

const myClass * const * Q

Which is the same as

const myClass* const *Q

The first const applies to myClass, and the second const applies to the first *.

Declarations are read in right-to-left order (see Easy rule to read complicated const declarations?), so this declaration basically says:

"Q is a non-const pointer to a const pointer to a const MyClass object"

Basically, meaning that the value of Q itself can be changed (good for iterating), but the value of the myClass* pointer that Q is pointing at can't be changed, and the myClass object that is being pointed at by the myClass* pointer can't be changed either (only const methods can be called on it).

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks - your explanation is very helpful. Don't know why my the original developer of this code had to write it in such an obscure way... : ( – Todd Hoatson Nov 09 '17 at 20:37
  • @ToddHoatson: probably just being over-cautious. If `begin()` actually did return a pointer as the iterator, `Q` could be declared simply as `myClass**` instead. But iterators are an abstraction, best not to assume an iterator is ever implemented using just a pointer (even though that is allowed). – Remy Lebeau Nov 09 '17 at 20:41
0

Below example will clear your doubt regarding const myClass *const* Q:

#include <iostream>
using namespace std;

int main(void)
{
    int i = 10;
    int j = 20;
    int *pt = &j;

    /* constant pointer to constant integer */
    const int *const ptr = &i;  

    /* constant pointer to constant poniter */
    const int *const * pptr = &pt;  

    cout<< " Address of i :" << &i << "    Address of J : " << &j <<endl;
    cout<< " value of i :  " << i << "    value of J : " << j << endl;
    cout<<" pptr :   " << pptr << endl;
    cout<<" ptr  :   " << ptr << endl;  // ptr is point to i
    cout<<" *pptr :   " << *pptr << endl; // *pptr is pointing j
    cout<<" *ptr  :   " << *ptr << endl;
    cout<<" **pptr :   " << (*(*pptr)) << endl;

    return 0;
}

Output ::

Address of i :0x22fe3c    Address of J : 0x22fe38
value of i :  10    value of J : 20
pptr :   0x22fe30
ptr  :   0x22fe3c
*pptr :   0x22fe38
*ptr  :   10
**pptr :   20

Hope you are clear now.

What I do know is that this code generally assumed that vector.begin() returns a pointer rather than an iterator

You are wrong here, it returns an iterator. Please refer to vector::begin() reference.

(check your myClassMethod function ((*Q)->myClassMethod();) ) and refer to error C2678: binary '=' : no operator found which takes a left-hand operand of type 'const Recipe' (or there is no acceptable conversion)

My suggestion : use latest C++11/C++14 feature (Like auto, ...).

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • I have added the declaration of myClassMethod() to my question, above. It is declared as const, so it shouldn't be a problem to call it using "a non-const pointer to a const pointer to a const MyClass object", right? Also, the legacy code compiled cleanly in Visual C++ 6.0. To jump directly to C++11/14 would be too great a step. Better to take an intermediate step to VS 2003 first... – Todd Hoatson Nov 09 '17 at 20:35
  • @ToddHoatson: yes, you can call a `const` method (like `myClassMethod()`) via a `const myClass*` pointer (in fact, that is ALL you can call) – Remy Lebeau Nov 09 '17 at 20:43
  • So @RemyLebeau, any ideas why I'm getting this error using !=...? – Todd Hoatson Nov 09 '17 at 20:49
  • The error message says the iterator variable on the left side of `!=` is using `CQueryThing*` instead of `myClass*`. Did you rename the types in your question, or is that a legitimate mismatch in your real code? – Remy Lebeau Nov 09 '17 at 20:50
  • Sorry, renamed things in the code for clarity, but forgot to edit the error message... I've changed it in the question, above. – Todd Hoatson Nov 09 '17 at 21:16
  • Looking back at the code, I found that I had copied the vector definition incorrectly. It is a vector of const pointers. So I updated the question above, which then led to a new issue... (see above) – Todd Hoatson Nov 09 '17 at 21:29
0

You actually have const in two places, with somewhat different effects.

for (const myClass *const* Q = vMyClass.begin(); Q != vMyClass.end(); Q++)
     ^^1^^          ^^2^^

First, let's separate out the declaration from the rest:

const myClass *const* Q

Let's start by making this consistent, and putting each const after what it modifies. In this case, that means moving only the first one:

myClass const *const* Q

For a declaration like this, we want to start from the name of the object being declared, and read "*" as "pointer to a", so this is read as something like this:

 Q is a pointer to a const pointer to a const myClass

The problem you have doesn't really relate to const at all. The problem you have is that the author of the code depended on the fact that in some particular library, std::vector<T>::iterator was (apparently) implemented as a pointer to T. This is allowable, but has some undesirable side effects (e.g., it means vector<derived>::iterator can be implicitly converted to vector<base>::iterator) so modern implementations generally avoid it.

If you want to convert this code to modern C++, all that is sort of beside the point though--you probably want to use something like this:

for (auto Q : vMyClass)
    Q->myClassMethod();
Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111