I stumbled upon an unexpected behavior of a shared pointer I'm using.
The shared pointer implements reference counting and detaches (e.g. makes a copy of), if neccessary, the contained instance on non-const usage.
To achieve this, for each getter function the smart pointer has a const and a non-const version, for example: operator T *() and operator T const *() const.
Problem: Comparing the pointer value to nullptr leads to a detach.
Expected: I thought that the comparison operator would always invoke the const version.
Simplified example:
(This implementation doesn't have reference counting, but still shows the problem)
#include <iostream>
template<typename T>
class SharedPointer
{
public:
inline operator T *() { std::cout << "Detached"; return d; }
inline operator const T *() const { std::cout << "Not detached"; return d; }
inline T *data() { std::cout << "Detached"; return d; }
inline const T *data() const { std::cout << "Not detached"; return d; }
inline const T *constData() const { std::cout << "Not detached"; return d; }
SharedPointer(T *_d) : d(_d) { }
private:
T *d;
};
int main(int argc, char *argv[])
{
SharedPointer<int> testInst(new int(0));
bool eq;
std::cout << "nullptr == testInst: ";
eq = nullptr == testInst;
std::cout << std::endl;
// Output: nullptr == testInst: Detached
std::cout << "nullptr == testInst.data(): ";
eq = nullptr == testInst.data();
std::cout << std::endl;
// Output: nullptr == testInst.data(): Detached
std::cout << "nullptr == testInst.constData(): ";
eq = nullptr == testInst.constData();
std::cout << std::endl;
// Output: nullptr == testInst.constData(): Not detached
}
Question 1: Why is the non-const version of the functions called when it should be sufficient to call the const version?
Question 2: Why can the non-const version be called anyways? Doesn't the comparison operator (especially comparing to the immutable nullptr) always operate on const references?
For the record:
The shared pointer I'm using is Qt's QSharedDataPointer holding a QSharedData-derived instance, but this question is not Qt-specific.
Edit:
In my understanding, nullptr == testInst would invoke
bool operator==(T const* a, T const* b)
(Because why should I compare non-const pointers?)
which should invoke:
inline operator const T *() const
Further questions:
Why isn't it the default to use the const operator?
Is this because a function cannot be selected by the type of the return value alone?
=> This is answered in Calling a const function rather than its non-const version
So this question boils down to:
Why doesn't the default implementation of the comparison operator take the arguments as const refs and then call the const functions?
Can you maybe cite a c++ reference?
When there exists an overload on const and non-const, the compiler will always call non-const version if the object you're using is non-const. Otherwise, when would the non-const version ever be invoked?
If you want to explicitly use the const versions, invoke them through a const reference:
const SharedPointer<int>& constRef = testInst;
eq = nullptr == constRef;
In the context of Qt's QSharedDataPointer, you can also use the constData function explicitly whenever you need a pointer.
For the intended usage of QSharedDataPointer, this behavior is not usually a problem. It is meant to be a member of a facade class, and thus used only from its member functions. Those member functions that don't need modification (and thus don't need detaching) are expected to be const themselves, making the member access to the pointer be in a const context and thus not detach.
Edit to answer the edit:
In my understanding, nullptr == testInst would invoke
bool operator==(T const* a, T const* b)
This understanding is incorrect. Overload resolutions for operators is rather complex, with a big set of proxy signatures for the built-in version of the operator taking part in the resolution. This process is described in [over.match.oper] and [over.built] in the standard.
Specifically, the relevant built-in candidates for equality are defined in [over.built]p16 and 17. These rules say that for every pointer type T, an operator ==(T, T) exists. Now, both int* and const int* are pointer types, so the two relevant signatures are operator ==(int*, int*) and operator ==(const int*, const int*). (There's also operator ==(std::nullptr_t, std::nullptr_t), but it won't be selected.)
To distinguish between the two overloads, the compiler has to compare conversion sequences. For the first argument, nullptr_t -> int* and nullptr_t -> const int* are both identical; they are pointer conversions. Adding the const to one of the pointers is subsumed. (See [conv.ptr].) For the second argument, the conversions are SharedPointer<int> -> int* and SharedPointer<int> -> const int*, respectively. The first of these is a user-defined conversion, invoking operator int*(), with no further conversions necessary. The second is a user-defined conversion, invoking operator const int*() const, which necessitates a qualification conversion first in order to call the const version. Therefore, the non-const version is preferred.
Maybe this code will allow you to understand what happens:
class X {
public:
operator int * () { std::cout << "1\n"; return nullptr; }
operator const int * () { std::cout << "2\n"; return nullptr; }
operator int * () const { std::cout << "3\n"; return nullptr; }
operator const int * () const { std::cout << "4\n"; return nullptr; }
};
int main() {
X x;
const X & rcx = x;
int* pi1 = x;
const int* pi2 = x;
int* pi3 = rcx;
const int* pi4 = rcx;
}
The output is
1
2
3
4
If the const object (or reference to it) is casted, the const cast operator (case 3 and 4) is choosen, and vice versa.
This is because of how the expression testInst == nullptr is resolved:
Let's look at the types:
testInst is of type SharedPointer<int>.
nullptr is (for the sake of simplification) of type T* or void*, depending on the use case.
So the expression reads SharedPointer<int> == int*.
We need to have equal types to invoke a comparison operator. There are two possibilities:
Resolve to int* == int*.
This involves a call to operator int *() or operator int const *() const.
[citation needed]
Resolve to SharedPointer<int> == SharedPointer<int>
This involves a call to SharedPointer(nullptr).
Because the second option would create a new object, and the first one doesn't, the first option is the better match. [citation needed]
Now before resolving bool operator==(int [const] *a, int [const] *b) (the [const] is irrelevant), testInst must be converted to int*.
This involves a call to the conversion operator int *() or operator int const *() const.
Here, the non-const version will be called because testInst is not const. [citation needed]
I created a suggestion to add comparison operators for T* to QSharedDataPointer<T> at Qt Bugs: https://bugreports.qt.io/browse/QTBUG-66946
Related
I tried to wrap something similar to Qt's shared data pointers for my purposes, and upon testing I found out that when the const function should be called, its non-const version was chosen instead.
I'm compiling with C++0x options, and here is a minimal code:
struct Data {
int x() const {
return 1;
}
};
template <class T>
struct container
{
container() {
ptr = new T();
}
T & operator*() {
puts("non const data ptr");
return *ptr;
}
T * operator->() {
puts("non const data ptr");
return ptr;
}
const T & operator*() const {
puts("const data ptr");
return *ptr;
}
const T * operator->() const {
puts("const data ptr");
return ptr;
}
T* ptr;
};
typedef container<Data> testType;
void testing() {
testType test;
test->x();
}
As you can see, Data.x is a const function, so the operator -> called should be the const one. And when I comment out the non-const one, it compiles without errors, so it's possible. Yet my terminal prints:
"non const data ptr"
Is it a GCC bug (I have 4.5.2), or is there something I'm missing?
If you have two overloads that differ only in their const-ness, then the compiler resolves the call based on whether *this is const or not. In your example code, test is not const, so the non-const overload is called.
If you did this:
testType test;
const testType &test2 = test;
test2->x();
you should see that the other overload gets called, because test2 is const.
test is a non-const object, so the compiler finds the best match: The non-const version. You can apply constness with static_cast though: static_cast<const testType&>(test)->x();
EDIT: As an aside, as you suspected 99.9% of the time you think you've found a compiler bug you should revisit your code as there's probably some weird quirk and the compiler is in fact following the standard.
It doesn't matter whether Data::x is a constant function or not. The operator being called belongs to container<Data> class and not Data class, and its instance is not constant, so non-constant operator is called. If there was only constant operator available or the instance of the class was constant itself, then constant operator would have been called.
But testType is not a const object.
Thus it will call the non const version of its members.
If the methods have exactly the same parameters it has to make a choice on which version to call (so it uses the this parameter (the hidden one)). In this case this is not const so you get the non-const method.
testType const test2;
test2->x(); // This will call the const version
This does not affect the call to x() as you can call a const method on a non const object.
It seems it is in VS2013. But why in effective c++' item 3, the non-const operator calls const operator when they do exactly the same thing?
This is the code in Effective c++ item 3:
class TextBlock {
public:
...
const char& operator[](std::size_t position) const // same as before
{
...
...
...
return text[position];
}
char& operator[](std::size_t position) // now just calls const op[]
{
return const_cast<char&>( // cast away const on type;
static_cast<const TextBlock&>(*this)[position] // add const to *this's type;call const version of op[]
);
}
...
};
A const object cannot use a non-const method. (Remember that operators are only methods).
But the converse is true: A non-const object can use a const method (although it will use a non-const equivalent if that's present).
A method is only const if it is marked as const: a compiler is not allowed to assert const-ness by inspecting the function body.
Yes: if a const member function is present, but no non-const version, then calling that function on a non-const object will use the const function.
#include <iostream>
struct Foo {
void bar() const { std::cout << "const bar()\n"; }
};
int main() {
Foo f;
f.bar();
}
prints const bar().
Note the reverse is not true. You may not call a non-const member function on a const object:
struct Foo {
void bar() { std::cout << "non-const bar()\n"; }
};
int main() {
const Foo f;
f.bar();
}
gives a compiler error:
SO.cpp: In function 'int main()':
SO.cpp:9:11: error: passing 'const Foo' as 'this' argument of 'void Foo::bar()' discards qualifiers [-fpermissive]
f.bar();
^
EC++ Item 3, Use const whenever possible has a subsection, Avoiding Duplication in const and Non-const Member Functions. The reason it suggests that you don't just let the compiler pick the const version is that the return types are different (by a const) - the non-const version casts this away:
class CharWrapper {
char c_;
public:
char const& getC() const
{
std::cout << "const getC()\n";
return c_; // pretend this line is complicated
}
char& getC()
{
std::cout << "non-const getC()\n";
char const& c = const_cast<CharWrapper const&>(*this).getC(); // call the right one to avoid duplicating the work
return const_cast<char&>(c); // fix the result type
}
};
int main() {
CharWrapper wrapper;
wrapper.getC() = 'a';
}
Notice that it calls const operator and casts away constness (it one of those few times where it does not result in UB). It is needed so non-const operator returns a mutable reference. Otherwise you would not be able to modify objects contained in vector at all.
Call to existing operator instead of writing code again is done to avoid code duplication and possible future errors when you change one function, but forgot the other.
Regarding your comment: there is no move involved. It returns a reference. I would expect no difference in generated code for both const and non-const operator.
Recently I was reading through the API of boost::optional and came across the lines:
T const& operator *() const& ;
T& operator *() & ;
T&& operator *() && ;
I also wrote my own program that defines member functions as const&, & and && (Note that I am not speaking about the return type, but the specifiers just before the semi-colons) and they seems to work fine.
I know what it means to declare a member function const, but can anyone explain what it means to declare it const&, & and &&.
const& means that this overload will be used only for const, non-const and lvalue objects, such as:
const A a = A();
*a;
& means that this overload will be used only for non-const objects:
A a;
*a;
&& means that this overload will be used only for rvalue objects:
*A();
For more information about this feature of the C++11 standard you can read this post: What is "rvalue reference for *this"?
It is a member function ref-qualifiers; it is one of the features added in C++11. It is possible to overload non-static member functions based on whether the implicit this object parameter is an lvalue or an rvalue by specifying a function ref-qualifier (some details).
To specify a ref-qualifier for a non-static member function, you can either qualify the function with & or &&.
#include <iostream>
struct myStruct {
void func() & { std::cout << "lvalue\n"; }
void func() &&{ std::cout << "rvalue\n"; }
};
int main(){
myStruct s;
s.func(); // prints "lvalue"
std::move(s).func(); // prints "rvalue"
myStruct().func(); // prints "rvalue"
}
I've read the code in wtf about safebool, but I cannot understand the grammar.
Here is the code from wtf in webkit:
// This conversion operator allows implicit conversion to bool but not to other integer types.
typedef T* (RefPtr::*UnspecifiedBoolType);
operator UnspecifiedBoolType() const { return m_ptr ? &RefPtr::m_ptr : 0; }
typedef T* (RefPtr::*UnspecifiedBoolType);
What does this mean?
We define a new type or something else? What does int braces above means?
You didn't give us link to full code, I hope I found this particular. Ok. If you don't know what typedef mean, I think the easiest description is on cppreference.com, let me quote.
http://en.cppreference.com/w/cpp/language/typedef :
The typedef declaration provides a way to create an alias that can be used anywhere in
place of a (possibly complex) type name.
It is true that this is a non-trivial example. From now UnspecifiedBoolType is a alias of Type T* RefPtr::*, it's a "pointer to member". I would be grateful for a link to a good description of pointers to member. I regret, but I do not have something for beginners.
Analogical, but easier situation is here. Q is now alias to type T* my_class::*, it's a pointer to Member. (check confirms the correctness)
template <typename T>
class my_class {
typedef T* (my_class::*Q);
public:
void check() {
std::cout << typeid(Q).name() << '\n';
std::cout << "Q == (T* my_class<T>::*) is " << std::boolalpha
<< (typeid(Q) == typeid(T* my_class<T>::*)) << '\n';
}
};
And it prints for:
my_class<int> my_object;
my_object.check();
M8my_classIiEPi
Q == (T* my_class::*) is true
The remaining second line, about which you asked.
operator UnspecifiedBoolType() const { return m_ptr ? &RefPtr::m_ptr : 0; }
From what I can see m_ptr is a T* type. (Pointer to T objcet.) So, it's a definition of operator UnspecifiedBoolType(), which is a conversion function (it transforms object to object of another type, here RefPtr to UnspecifiedBoolType {aka T* RefPtr::*}), here return is pointer to member m_ptr, when m_ptr isn't null pointer or null if it is.
if I wan't to define this without typedef ...
It's a conversion function, similar to this:
class my_class {
bool is_true;
public:
operator bool() const { //conversion to bool
return is_true;
}
};
if(object_of_my_class) is true, when is_true is true, ;) (in object_of_my_class) because if expected bool. There you are converting to the type UnspecifiedBoolType {aka T* RefPtr::*}. There is one more issue.
// This conversion operator allows implicit conversion to bool but not to other integer types.
The above function, even though is defined only to bool conversion, works for some other types. In base example, it isn't. This is because pointer to member has explicit converse operator (pointer to member conversion). Good explanation of "explicit" is here. On example, the last code, if we edit conversion function to :
explicit operator bool() const { //conversion to bool only
return is_true;
}
Now not allowed is implicit conversion. For example :
my_class object;
bool b(object); //Ok, everything is correctly
//int i(object); //It's illegal!
//bool b2 = object; //Still illegal.
First commented out line give us :
error: cannot convert ‘my_class’ to ‘int’ in initialization
Second behaves similarly. But you can still do something like this:
bool b3 = bool(object); //or even
int i2 = bool(object); //or
int i3 = static_cast<bool>(object); //but
//int i4 = reinterpret_cast<bool>(object); //is still unacceptable.
error: invalid cast from type ‘my_class’ to type ‘bool’
Cause is the moment of cast.
I define two versions of overloaded operator[] function in a class array. ptr is a pointer to first element of the array object.
int& array::operator[] (int sub) {
return ptr[sub];
}
and
int array::operator[] (int sub) const {
return ptr[sub];
}
Now, if I define a const object integer1 the second function can only be called..... but if I make a non-const object and then invoke as below:
cout << "3rd value is" << integer1[2];
which function is called here?
In your second example, the non-const version will be called, because no conversion is required, and a call that requires no conversion is a better match than one that requires a conversion.
Ultimately, however, you have a basic problem here: what you really want is behavior that changes depending on whether you're using your object as an rvalue or an lvalue, and const doesn't really do that. To make it work correctly, you normally want to return a proxy object, and overload operator= and operator T for the proxy object:
template <class T>
class myarray {
T *ptr;
class proxy {
T &val;
proxy &operator=(proxy const &p); // assignment not allowed.
public:
proxy(T &t) : val(t) {}
operator T() const { return val; }
proxy &operator=(T const&t) { val = t; return *this; }
};
proxy const operator[](int sub) const { return proxy(ptr[sub]); }
proxy operator[](int sub) { return proxy(ptr[sub]); }
// obviously other stuff like ctors needed.
};
Now we get sane behavior -- when/if our array<int> (or whatever type) is const, our operator[] const will be used, and it'll give a const proxy. Since its assignment operators are not const, attempting to use them will fail (won't compile).
OTOH, if the original array<int> was not const, we'll get a non-const proxy, in which case we can use both operator T and operator=, and be able to both read and write the value in the array<int>.
Your const version should return const int& not int, so that the semantics are just the same between the two functions.
Once you've done that, it doesn't matter which one is used. If the const version has to be used because your object has a const context, then it will be... and it won't matter as you're not trying to modify anything. Otherwise, it'll use the non-const version... but with just the same effect.