Template containers (e.g. QList) not feasible for pointers (some C++ gurus out there?)
-
There's no nice way to do this in C++. You'll find the same issue with standard C++ containers too, which use std::find(), which is even messier than Qt's indexOf(). I suppose you could define a template specifically for pointers...
@
template <typename T> class PointerList
{
public:
int indexOf(const T* const value) const { /.../ }
};class A
{
public:
int Row1() const { return parent->children.indexOf(this); } // This would compileprivate:
A* parent;
PointerList<A> children;
};
@...but that requires an unconventional "PointerList<A>" declaration (without '*'), which I think is unintuitive. Between this method and const_cast-ing, which is "better" is up for philosophical debate.
-
@Vass:
That may be the designed behaviour of C++ but I don't like it. That const_cast shouldn't be necessary.@JKSH:
I see. I think the "PointerList<A>" declaration would be a bit more inutitive than the const_cast. But duplicating each container for pointer types would be a lot of effort. Too much effort.
So this is weakness in C++? Lets get even more theoretical... how could this be solved on language level? How does other generic languages prevent this? I think C# doesn't support const-ness at all, is this right? -
And this problem only occurs if you have a container that contains a this pointer and access it in a const function.
If you have a standard container containing pointers (none const ones) and ask with find (or indexOf) and also use none const pointer everything is find.
Your problem is you are looking for a this pointer in a const method.
-
[quote author="silicomancer" date="1346334361"]I think the "PointerList<A>" declaration would be a bit more inutitive than the const_cast. [/quote]
This container also has a leak: try to create a container of const pointers with it. It will result in a method
@
int indexOf(const T* const value) const { /.../ }// will become
int indexOf(const const MyPtrClass* const value) const { /.../ }
@whic will create a compiler error :-(
-
[quote author="silicomancer" date="1346334361"]So this is weakness in C++? Lets get even more theoretical... how could this be solved on language level? How does other generic languages prevent this? I think C# doesn't support const-ness at all, is this right?[/quote]You could say that C# doesn't encounter this issue because it's lax with const-ness. If you're not on the lookout for a problem, you're less likely to find one! That leads to other const-ness issues for developers to grapple with: http://stackoverflow.com/questions/114149/const-correctness-in-c-sharp
[EDIT]: Found this quote in the SO discussion: "Reference itself is immutable unless you specify ref modifier. But referenced data isn't immutable." Sounds just like the situation you've encountered in C++!
As for how to solve it on the language level... I'm not sure. Never tried to design my own language. But this issue is caused by different "multiplicities" -- the template type could contain information for 1 value (data only) or 2 values (data + pointer), as implicated in your linked article. How to handle all possible combinations of const-choices with 1 keyword?
In C++, the compiler takes this simple approach:
@
if (HAS_CONST_KEYWORD)
{
if (IS_POINTER)
MAKE_POINTER_CONST();
else
MAKE_DATA_CONST();
}
@What we'd need is a way to provide the compiler with instructions -- in a compact yet detailed way -- to follow this decision tree:
@
if (HAS_CONST_KEYWORD)
{
if (IS_POINTER)
{
switch (POINTER_CONST_AFFINITY)
{
case POINTER_AND_DATA:
MAKE_POINTER_CONST();
MAKE_DATA_CONST();
break;
case DATA_ONLY:
MAKE_DATA_CONST();
break;
default: // POINTER_ONLY, as currently supported by C++
MAKE_POINTER_CONST();
}
}
else
{
MAKE_DATA_CONST();
}
}
@ -
[quote author="Gerolf" date="1346335083"]This container also has a leak: try to create a container of const pointers with it. It will result in a method
@
int indexOf(const T* const value) const { /.../ }// will become
int indexOf(const const MyPtrClass* const value) const { /.../ }
@whic will create a compiler error :-([/quote]Whoops! Back to my original post I guess: There is no nice way to do this in C++. It doesn't mean C++ is a poorly-designed language, but that there's no way for a complex set of rules to cover all possibilities (just look at human legislation)
@silicomancer:
You could un-const your Row1() function, if you're willing -
Very interesting thoughts. Thanx!
I already considered removing const-ness from the Row1() function. But I fact this will trigger a cascade since I use the function in other const-functions, so this only would defer the problem (but multiply it).
I will stick to the const-cast and try to ignore it ;-)
I just had another idea. What is about using smart pointers as arguments? For a smart pointer there is no difference between "const smart_ptr<Type> ptr;" and "smart_ptr<Type> const ptr;", is it? I never used smart pointers in typdefs or templates... but I guess when using a smart pointer as indexof() parameter it could clearly dinstinguish between pointer-to-const an const-pointer. Is this right? Is this a solution?
-
No problem. May you retain your sanity in const hell!
[quote author="silicomancer" date="1346493063"]For a smart pointer there is no difference between "const smart_ptr<Type> ptr;" and "smart_ptr<Type> const ptr;", is it?[/quote]You're right about those being the same. Unfortunately, that doesn't help you because they're both const-pointers, not const-pointers-to-const-data.
@
QList<A*> raw_list;
QList< std::shared_ptr<A> > smart_list;// ...
raw_list.indexOf(/.../); // Expects const-pointer-to-NON-const-data
smart_list.indexOf(/.../); // Expects const-pointer-to-NON-const-data
@ -
Here's a possible approach, if you're willing to put up with a redundant member variable, and a potential security leak:
@
class A
{
public:
A()
{
// ...
selfAddress = this;
}
int Row1 const { return parent->children.indexOf(self); }private:
QList<A*> children;
A* parent;
A* const self; // Dangerous!
}
@I haven't tested it, but it might break const correctness -- I don't think the compiler would realize if you try to modify a member variable using the "self" pointer in a const function. It would stop you if you tried it via "this".
Edit: In hindsight, this approach works by creating the same kind of "const back-door" as const_cast. Only, const_cast is safer because the back door is restricted to the place where the casting occurred; the "self" pointer opens that back door in every const function!
Take-home message: Some tools are hazardous if mis-used. However, when used responsibly, they make life very nice. const_cast is one such tool (a chainsaw is another). We should choose the least dangerous of the tools that can do what we want, and use it carefully.
-
Yeah. You are right.
There is one question I though about yesterday... what exactly is the reason why const-ness of a pointer and of its pointed object aren't coupled in C++? Is there something important we couldn't do using C++ if a pointer has the const-ness of its object?
Hm. Yes, pointer arithmetics. Rarely used but impossible without separate pointer-const-ness.
Something more you can think of? -
You say pointer arithmetic is rarely used, but it's used all the time in implementations (think of how arrays work).
Pointers are values, they just happen to be values that point to other values (memory addresses). Values can be const or non-const, so you have the const-ness of the pointer, and the const-ness of what that pointer points to.
For example: Non-const pointers to const objects allow manipulation of the pointer, but protect the data pointed to. Const pointers to non-const objects protect the value of the pointer from changing, while allowing manipulation of the data pointed to.
Although it can be a headache sometimes, this gives flexibility and allows the programmer to express their intent more clearly to the compiler, so it can warn you when you're doing something wrong. -
Array operations? They don't change the pointer itself.
Hm. Yes. Its flexibel. But I never needed the flexibility. And I have never seen code that needed that flexibility (except one or two legacy C projects using the mentioned pointer arithmetics e.g. incrementing a pointer instead of using []). But currently I have seen a pretty nasty problem caused by that detail. And I have heared a lot of people being frustated about it.
However this is very theoretical since I am sure this will never change in C++ :-)
Ok, lets finish this. It seems there is no way around that nasty const-cast and it looks like there even wouldn't be a good solution on language level. A little disappointing though.
-
Maybe the question is weird, but looking at the original code you posted, I do not understand the use case:
@
class A // e.g. a model item
{
public:
int Row1() const { return parent->children.indexOf(this); }
int Row2() const { return parent->children.indexOf(const_cast<A*>(this)); }private: QList<A*> children; A* parent; };
@
if chidlren are you children, you can just use
@
int Row1() const { return parent->children.count(); }
@if children is a complete tree, or the list of element, which are my siblings, then the naming is weird and the usage too.
Typically, you would ask your parent about your row number or you have a member row.
But containing a list of children where you search your own row number looks not very convenient. -
Yes. That code is a simplified snippet.
In the real code, the child asks its parent about the row which is the way QAbstractItemModel works.
-
I wasn't referencing how raw (C-style) arrays work, but how array-like classes may be implemented in C++