Template containers (e.g. QList) not feasible for pointers (some C++ gurus out there?)
-
Yeah, sounds stupid. But its true. I just discovered a nasty behaviour of C++ template containers when used with pointers as types. Look at the following. It's a simple code like it is needed when implementing abstract model items in Qt (code is simplified):
@
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;
};
@Row1 will fail to compile due to wrong const-ness. Row2 will work. However Row1 should compile too, since indexOf() does not modify the passed argument.
I don't want to ask why this happens. I already found an explanation in http://www.drdobbs.com/conversationsa-midsummer-nights-madness/184403835 (it is hard to understand but it is definitely the same problem even if the linked article is talking about typedefs instead of template typenames).
I also don't want to discuss a workaround for my code since Row2() seems to be the only workaround.
What I would like to know: What could Trolltech have done better when implementing QList to make it fit for pointer handling by avoiding the above const-ness problem? Since this is actually a C++ issue, maybe there is no solution but I refuse to accept this.
-
If you already find explanation, why you ask about this problem?
In C++ in const methods this has type const T* const
method indexOf of QList class need has parameter type const T &
C++ not allowed implicit convertion const variable to non-const variable.
What is problem? -
The problem is that indexOf() simply doesn't work for pointer types they way it does for other types.
I don't understand your statement. Do you think it is a good way, forcing developers to use const_cast to remove const-ness from a value that is passed to a const argument? I don't think so.
However I have no idea how to make it better. So I am asking here for a better way to implement something like QList without need for such nasty const_casts when using pointers.
So, do you have any idea how to do it right from the start? How could QList be enabled using pointer without nasty casts?
-
indexOf() work for pointers, but this has a const pointer to const object type inside const method.
-
Yes, right, right. I already understood what is going on. You don't need to repeat. That doesn't change the fact that the container doesn't work as expected when using pointers.
People (at least me) would expect the following two functions being the same:
@
template <typename T> void FT(const T& t) { } // With T = int*void F(const int*& t) { }
@But they are not the same:
@
const int y = 10;
const int* x = &y;F(x); // This works
FT<int*>(const_cast<int*>(x)); // This needs const_cast
@So what can be done to achieve the expected behaviour in QList?
-
Ok, try again
-const T& (with int*) should be read as "reference to const pointer to int"-
-const int*& should be read as "reference to const pointer to int"--Ok, you are right, it is same, but-
-const int* x should be read as "pointer to const int"-
-So! Compiler can't convert ponter to const to reference to const pointer-
-
-
Did you read the link I posted at the beginning?
Still don't know how to get rid of the const_cast.
-
Ok, I looked on it. Seems same.
So then I don't understand you,
You wrote that this parameters not same,
You give me explanation of it, you (as I guess) understood explanation.But, however, you expect behaviour of language(not Qt, it is a language behaviour) which are not match your explanation.
-
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.