This question discusses how the arrow operator ->
is automatically re-applied to the return value of an overloaded operator->()
until the value returned is a raw pointer, at which point the raw pointer is dereferenced as if with ->
. However, the same is not true when one has a pointer-to-pointer type that they wish to dereference to the base value - they must use (*ptr_to_ptr)->foo()
instead. It seems to me that usage as ptr_to_ptr->foo()
would be unambiguous, even more so than the automatic re-application of ->
onto returned values until a raw pointer is returned. So, what was the reasoning behind this decision?
minimal working example:
#include <iostream>
struct Dog {
void bark() { std::cout << "woof!" << std::endl; }
};
struct DogWalker {
Dog* dog;
Dog* operator->() {
return dog;
}
};
struct DogOwner {
DogWalker walker = { new Dog() };
DogWalker operator->() {
return walker;
}
};
void main()
{
DogOwner owner;
owner->bark(); // works, prints "woof"
Dog** ptr_to_ptr = new Dog*;
*ptr_to_ptr = new Dog;
(**ptr_to_ptr).bark(); // works
(*ptr_to_ptr)->bark(); // works
//ptr_to_ptr->bark(); // ERROR
//C2227: left of '->bark' must point to class/struct/union/generic type
}
The language adopts much of its semantics from C. The ->
operator when applied to a pointer type is only valid if the pointer points to a non-array composite type. Since C doesn't have classes, C++ defined its own semantics for the overloaded ->
that made sense for the smart pointer use cases.
You can achieve the behavior you want with a helper class.
template <typename T>
struct Unwrap {
T *p_;
Unwrap (T *p = 0) : p_(p) {}
T * operator -> () const { return p_; }
};
template <typename T>
struct Unwrap<T *> {
T **p_;
Unwrap (T **p = 0) : p_(p) {}
Unwrap<T> operator -> () const { return *p_; }
};
template <typename T>
Unwrap<T> make_unwrap (T *p) { return p; }
You can then use it like this:
struct foo {
void bar () { std::cout << "Hello\n"; }
};
int main () {
foo p;
auto pp = &p;
auto ppp = &pp;
auto pppp = make_unwrap(&ppp);
pppp->bar();
}