user1225999
user1225999

Reputation: 920

Implicitly convert object to floating point type when arithmetics are performed with it

Assume we have a global object pi and we want to implicitly convert it to either float or double depending on the context. The following does not work:

#include <cmath>

class Pi {
public:
    Pi() {}

    operator float() const {
        return std::atan(1.0f)*4.0f;
    }

    operator double() const {
        return std::atan(1.0)*4.0;
    }
};

const Pi pi;

#include <iostream>
#include <iomanip>

int main() {
    std::cout << std::setprecision(50) << pi * 1.0f << std::endl;
    std::cout << std::setprecision(50) << pi * 1.0  << std::endl;
}

The reason why it does not work is that the compiler does not know whether it should implicitly convert pi to a float or a double. However, say we always want it to convert to the type of the other operand in a binary arithmetic operator. Is there some elegant way to achieve that in C++11 or later or should I overload all arithmetic operators? Something like that:

class Pi {
public:
    Pi() {}

    float operator*(float x) const {
        return (std::atan(1.0f)*4.0f) * x;
    }

    double operator*(double x) const {
        return (std::atan(1.0)*4.0) * x;
    }

    //...
};

Or can you come up with a more elegant solution for implicitly converting the global object to an appropriate type for the context?

Upvotes: 4

Views: 158

Answers (2)

Barry
Barry

Reputation: 302708

Honestly, I would just stick with the path of least surprise:

class Pi {
    ...
    explicit operator float() { ... }
    explicit operator double() { ... }
    ...
};

And just cast it when you want it:

std::cout << std::setprecision(50) << (float)pi * 1.0f << std::endl;

This is just a precursor for variable templates, in which you would want just write pi<float>. It's the same number of characters either way.

Upvotes: 2

AndyG
AndyG

Reputation: 41092

Your first try is close. Let's add a templated * operator outside the class:

template<typename T>
T operator*(const Pi& pi, T&& other)
{
    return static_cast<T>(pi) * std::forward<T>(other);
}

And now it works. Live Demo


Full Code

#include <cmath>
#include <iostream>
#include <iomanip>

class Pi {
public:
    Pi() {}

    operator float() const {
        return std::atan(1.0f)*4.0f;
    }

    operator double() const {
        return std::atan(1.0)*4.0;
    }
};

const Pi pi;

template<typename T>
T operator*(const Pi& pi, T&& other)
{
    return static_cast<T>(pi) * std::forward<T>(other);
}

int main() {
    std::cout << std::setprecision(50) << pi * 1.0f << std::endl;
    std::cout << std::setprecision(50) << pi * 1.0  << std::endl;
}

Output:

3.1415927410125732421875
3.141592653589793115997963468544185161590576171875


If you want to be really strict about what substitutes for T look into std::enable_if and std::is_floating_point within type_traits

Upvotes: 2

Related Questions