Search code examples
c++classoverloadingtemplatingcmath

How can I perform a cmath function on a custom class instance?


Are there any methods in C++ I can use, such as overloading or templating, that would allow me to pass class instances as an argument to a cmath function? For example, if I had a class named “Point” (shown below), is there any way that I could perform the operation std::abs(Point(-4, -9)) and have it return Point(4, 9)?

#include <iostream>
#include <cmath>

class Point{
    private:
        double x, y;

    public:
        Point(double x, double y) {
            this->x = x;
            this->y = y;
        }

        // Approach 1 that I would like to avoid 
        static Point abs1(const Point &p1) {
            return Point(std::abs(p1.x), std::abs(p1.y));
        }

        // Approach 2 that I would like to avoid 
        Point abs2(void) {
            return Point(std::abs(x), std::abs(y));
        }
};

int main()
{
    Point pt1(-4.0, -9.0), pt2;

    pt2 = std::abs(pt1) // <-- What I would like to be able to do

    pt2 = Point::abs1(point_d); // <-- Function call 1 that I would like to avoid
    pt2 = point_d.abs2(); // <-- Function call 2 that I would like to avoid

    return 0;
}

Or am I restricted to using class based methods that would require me calling Point::abs(Point(-4, -9)) or Point(-4, -9).abs()? So in short, can I augment the cmath function in anyway to accept a class instance?

I have had a look around and I can’t find any information on the subject, however I am quite new to C++. So, I would appreciate any information on how this might be done, if it can be done, and whether such an action is ill-advised, and if so, why?

Thanks in advance.


Solution

  • Have a look at this reference page:

    For calculating absolute values, in cmath you just have a bunch of overloads that operate on primitive types:

      int abs(int j);
      long int abs(long int j);
      long long int abs(long long int j);
      float abs(float j);
      double abs(double j);
      long double abs(long double j);
    

    Since these functions are not templated, there is no way you can pass to them a Point class and get back another instance of Point. They can only receive a primitive type, and return the same type.

    Something like this (similar in terms of syntax only) could only happen, if your Point class was convertible to one of these primitive types. E.g. in the following snippet, I defined class A, which is implicitly convertible to and from int, therefore when I call abs with an instance of A, it is automatically converted to int, passed to the appropriate overload of abs, and finally the result is converted back to A.

    #include <cmath>
    #include <iostream>
    
    
    class A
    {
    public:
        A(int x_)    // implicit conversion from int
        : x(x_)
        {}
    
        operator int()
        {
            return x; // implicit conversion to int
        }
    private:
    
        int x;
    };
    
    
    int Foo(int x)
    {
        return x * 2;
    }
    
    int main()
    {
        A a1(-2);
        A a2 = Foo(a1);
        A a3 = std::abs(a1);
    
        std::cout << "a2: " << a2 << "\n";
        std::cout << "a3: " << a3 << "\n";
    
        getchar();
    }
    

    But this is pretty much how far you can go with this trick, which I think doesn't cover what you want. I believe the best approach in your case is to create some utility function, that does what you want. I would rather go for a free function, instead of a static member, to not litter the class with unnecessary utility methods, i.e. something like this

    namespace PointUtils
    {
        Point abs(const Point& p);
    }