Search code examples
c++operator-overloadingwrappertemplate-meta-programmingauto

C++ overloading auto operator in struct/class wrapper


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.


Solution

  • 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.