As you know, operator-> is applied recursively to the return type of functions until an unhandled pointer is found. The only exception is when it is called by name, as in your code example.
You can take advantage of this and return your own proxy object. To avoid the script in your last code snippet, this object must satisfy several requirements:
Its type name must be closed to matrix<>::iterator , so external code cannot reference it.
Its construction / copying / assignment should be closed. matrix<>::iterator will have access to them due to the fact that he is a friend.
The implementation will look something like this:
template <...> class matrix<...>::iterator { private: class row_proxy { row_view *rv_; friend class iterator; row_proxy(row_view *rv) : rv_(rv) {} row_proxy(row_proxy const&) = default; row_proxy& operator=(row_proxy const&) = default; public: row_view* operator->() { return rv_; } }; public: row_proxy operator->() { row_proxy ret(); return ret; } };
The operator-> implementation returns a named object to avoid any loopholes due to guaranteed copying in C ++ 17. Code that uses the inline operator ( it->mem ) will continue to work. However, any attempt to call operator->() by name without discarding the return value will not compile.
Living example
struct data { int a; int b; } stat; class iterator { private: class proxy { data *d_; friend class iterator; proxy(data *d) : d_(d) {} proxy(proxy const&) = default; proxy& operator=(proxy const&) = default; public: data* operator->() { return d_; } }; public: proxy operator->() { proxy ret(&stat); return ret; } }; int main() { iterator i; i->a = 3;
After further consideration of my proposed solution, I realized that this is not as flawless as I thought. You cannot create an object of the proxy class outside the scope of iterator , but you can still bind a link to it:
auto &&r = i.operator->(); auto *d = r.operator->();
Thus, you can again apply operator->() .
The immediate solution is to qualify the proxy operator and apply it only to r values. For example, for my live example:
data* operator->() && { return d_; }
This will cause the two lines above to throw an error again, while using the iterator correctly still works. Unfortunately, this still does not protect the API from abuse, due to the availability of casting, mainly:
auto &&r = i.operator->(); auto *d = std::move(r).operator->();
This is a fatal blow to the whole case. This does not interfere.
So, in conclusion, there is no protection against calling directions to operator-> iterator object. In the best case, we can make the API very difficult to use incorrectly, while proper use remains easy.
If making row_view copies is expansive, that might be good enough. But you should consider this.
Another point to consider that I did not touch on in this answer is that a proxy server can be used to implement copy-on-write. But this class can be as vulnerable as the proxy server in my answer, unless great care is taken and a rather conservative design is used.