3

Imagine you have a simple 2D Point object with two setters and getters.

template <typename T>
class Point
{
public:
    Point(T x, T y);

    T getX() const;
    T getY() const;

    void setX(T x);
    void setY(T y);

private:
    T _x;
    T _y;
};

But I want to work with this class in more 'scriptable-like' syntax. Something like :

auto point = Point<double>(10, 10);
point.x = 20;
point.y = point.x + 10;

You will say, just use a struct with public variable :

template <typename T>
struct Point
{
    T x;
    T y;
};

Yes, but I want to keep the privacy of the parameters and extend the class with some methods. So another idea is to make a wrapper helper that add operator alias to the setters/getters :

template <typename T, typename Get,  Get(T::*Getter)() const,
                      typename Set, void(T::*Setter)(Set)>
struct ReadWrite
{
    ReadWrite(T& ptr) : ptr(ptr) {}

    inline void operator= (Set const& rhs)
    {
        (ptr.*Setter)(rhs);
    }

    inline Get operator()()
    {
        return (ptr.*Getter)();
    }

private:
    T& ptr;
};

OK, I just modify my Point class to do the work :

template <typename T>
class Point
{
public:
    Point(T x, T y);

    T getX() const;
    T getY() const;

    void setX(T x);
    void setY(T y);

private:
    T _x;
    T _y;

public:
     ReadWrite<Point<T>, T, &Point<T>::getX, T, &Point<T>::setX> x;
     ReadWrite<Point<T>, T, &Point<T>::getY, T, &Point<T>::setY> y;
};

By adding some arithmetics operators ( + - * / ), I can use it like that:

auto point = Point<double>(10, 10);
point.x = 20;
point.y = point.x + 10;

Here, point.x is ok in case of operator overloading in the form:

template <typename T, typename V> inline T operator+(ReadWrite<T> const& lhs, V const& rhs) { return lhs() + rhs; }
template <typename T, typename V> inline T operator-(ReadWrite<T> const& lhs, V const& rhs) { return lhs() - rhs; }
template <typename T, typename V> inline T operator*(ReadWrite<T> const& lhs, V const& rhs) { return lhs() * rhs; }
template <typename T, typename V> inline T operator/(ReadWrite<T> const& lhs, V const& rhs) { return lhs() / rhs; }

If I want use this syntax, but without parenthesis on point.x getter :

auto point = Point<double>(10, 10);
auto x = point.x();

I extend the ReadWrite helper with:

template <typename T, typename Get,  Get(T::*Getter)() const,
                      typename Set, void(T::*Setter)(Set)>
struct ReadWrite
{
    ReadWrite(T& ptr) : ptr(ptr) {}

    inline void operator= (Set const& rhs)
    {
        (ptr.*Setter)(rhs);
    }

    inline Get operator()()
    {
        return (ptr.*Getter)();
    }

    inline operator auto() -> Get
    {
        return operator()();
    }

private:
    T& ptr;
}; 

Now with no parenthesis:

double x = point.x; // OK, x is my x value (Point).
auto x = point.x;   // Wrong, x is my ReadWrite<T> struct.

What is wrong with the overloading of the auto operator?

Thank you very much for your answer.

MaxC2
  • 343
  • 3
  • 10
  • 1
    C++ doesn't have properties like, say, C# does. There's no really super-awesome way of doing that. Michael Litvin's answer using a `Property` template gets close, q.v.: https://stackoverflow.com/questions/8368512/does-c11-have-c-style-properties – Eljay Apr 05 '19 at 19:55
  • Note: I've used something like Michael Litvin's technique in order to set a breakpoint on when a variable (turned into property-esque-ish) is modified. Super useful as a debugging technique, but then I remove that code scaffolding when done. – Eljay Apr 05 '19 at 19:57
  • @DanielLangr It has a trailing return type. – NathanOliver Apr 05 '19 at 19:58
  • @NathanOliver But then `auto` should come before `operator`, shoudn't it? And what is the operator that definition overloads? – Daniel Langr Apr 05 '19 at 19:59
  • 5
    @DanielLangr In this case no because it is a conversion operator. The return type is the name of the function essentially. `inline operator auto() -> Get` is the same as just writing `inline operator Get() ` – NathanOliver Apr 05 '19 at 20:00
  • @DanielLangr This code compile and work with VS2017 in C++17 language. Except for the 'auto' consideration. – MaxC2 Apr 05 '19 at 20:01
  • 1
    @NathanOliver Didn't know that, thanks for clarification. – Daniel Langr Apr 05 '19 at 20:01
  • @NathanOliver OK, I understand. But do you think that it is possible to do the trick? auto that do the deduction of the type? (without trailing return type) – MaxC2 Apr 05 '19 at 20:03
  • 1
    @MaximeCoorevits No. I'm writing an answer right now explaining why. Just a minute for that. – NathanOliver Apr 05 '19 at 20:04
  • Note that `ReadWrite` is not currently copiable, but it's trivial to workaround that and not fundamental to your question. – Mooing Duck Apr 05 '19 at 20:11
  • However: Why do you need setters/getters? If they aren't doing validation, then they're just wasting time. – Mooing Duck Apr 05 '19 at 20:12
  • Yup, if you can go public, do it. If you need validation, create a wrapper for the specific validation. – Guillaume Racicot Apr 05 '19 at 20:40

1 Answers1

7

There is nothing wrong with your class. The problem is how auto deduces types. The thing you have to remember about auto is it basically follows the rules for template argument deduction and the main thing about that is no implicit conversion will be done. This means that in

auto x = point.x;

You say compiler, give me a variable named x that has the type of the initialization expression. In this case point.x is a ReadWrite<Point<T>, T, &Point<T>::getX, T, &Point<T>::setX> so that is the type x gets. The only way to change that is to change what the initialization expression returns.

Unfortunately I'm not sure how you could do that. Proxy objects don't play well with auto type deduction as we pick up their type, not what they are mimicking.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • Just to note that [P0672 “Implicit Evaluation of auto Variables”](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0672r0.pdf) tried to tackle the issue of implicit `auto` deduction. This proposal could solve the problem of proxy objects and `auto` (the classic example is of course `vector` and its [inner proxy](https://stackoverflow.com/questions/8399417/why-vectorboolreference-doesnt-return-reference-to-bool)). Need to check what happend to this proposal! – Amir Kirsh Oct 01 '20 at 21:26