Can I create a class/enum/typedef that behaves just like a number but also has members/overloaded implicit conversion?

I have a class called Base, which is basically just a double with a bunch of member functions and a long list of operators to make it behave like a number. Other than that, I also have a Type() member which returns an integer.

I want to keep the Base type because it makes my code more readable and distinguishes the base type from an ordinary double, but I also don't like having to overload every single operators, as this is tedious. Ideally, I'd use enum Base double and then declare a Base member function Type() to get the integer, but this isn't legal in c++, is it?

So how can I easily achieve double-like functionality of my class while having a simple member function that returns an int?

P.S. the Type() function is not a simple cast or rounding, it's a complex function that I'd like to keep as a member if possible for clarity.
Last edited on
If your class can construct itself from a double and cast itself to a double, that ought to be enough.

So, it need one of these constructors:
 
Base(double);


and one of these case operators:
 
operator double () const;


I want to keep the Base type because it makes my code more readable and distinguishes the base
You may be better off with a typedef:
 
typedef double MyCoolFloatingPointType;
So if I can cast to double and from double, then when I write something like this:

1
2
3
4
Base A = 1.0; // cast from double to Base
Base B = 2.0; // case from double to base

A += B; // no cast to/from double, and no operator overload - error? 


Will that work? How would that last line function?
Yes I'm thinking of going with a typedef, but then I can't have a member function, right?
A += B needs operator+=(const Base &) and you may want to add operator+=(double).

Yes I'm thinking of going with a typedef, but then I can't have a member function, right?
Right, but you may not need one. Do you have an example of one you think you need?
Something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <limits>
#include <stdexcept>

struct real
{
    real( double v = 0.0 ) : value(v) {}

    // implicit conversions
    operator double& () { return value ; }
    operator double () const { return value ; }
    operator long long () const
    {
        if( ( value < std::numeric_limits<long long>::min() ) ||
             ( value > std::numeric_limits<long long>::max() ) )
                throw std::out_of_range( "out of range of long long") ;

        return value ;
    }

    // EDIT:
    double* operator& () { return &value ; }
    const double* operator& () const { return &value ; }


    // other operations
    bool is_nan() const { return value != value ; }
    // etc.

    private: double value ;
};

#include <iostream>

int main()
{
    real r = 7.8 ;
    int i = r ;
    r += 23.6 ;
    if( r > i ) std::cout << "ok\n" ;
    // etc
}
Last edited on
JLBorges, thank you for that example. My issue is then, as with kbw's example, is that as far as I understand this will fail:

1
2
3
4
5
6
7
8
real r = 7.8;
real s = 23.6;

// no implicit conversion and no operator overload in the following:
r += s; 
r *= s;
r = r/s; 
//etc... 


kbw:
Right, but you may not need one. Do you have an example of one you think you need?


Well as the class stores a double, I was thinking of simply typedefing something like this:

typedef double Base

and then creating a custom global function:

int getType(Base&);

which returns the type value based on the input. However, I am trying to avoid situations where I have global functions that only operate on a single type. Since a typedef member function isn't possible, can I overload implicit conversion, so that I could then do this:

1
2
3
4
typedef double Base;
Base my_base = 1.1;
// define implicit conversion overload
int type_of_base = my_base; // not a simple removal of decimal values 


This would also be an acceptable solution...
Last edited on
> is that as far as I understand this will fail:

Those won't fail.

Though there would have been an ambiguity if it were const real s = 23.6 ;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
struct real
{
    real( double v = 0.0 ) : value(v) {}

    // implicit conversions
    operator double& () { return value ; }
    operator double () const { return value ; }

    double* operator& () { return &value ; }
    const double* operator& () const { return &value ; }

    // other operations
    bool is_nan() const { return value != value ; }
    // etc.

    private: double value ;
};

#include <iostream>

int main()
{
    real r = 7.8;
    real s = 23.6;

    // the implicit conversion to double& in conjunction with
    // the implicit conversion via constructor will take care of these:
    r += s ; std::cout << r << '\n' ; // 31.4
    r *= s ; std::cout << r << '\n' ; // 741.04
    r = r/s ; std::cout << r << '\n' ; // 31.4

    // remove the conversion to long long and these too are fine
    // (we avoid the anbiguity - real to double or real to long long)
    // (but we lose the range check for conversion to an integral value)
    const real t = 6.7 ;
    r += t ; std::cout << r << '\n' ; // 38.1
    r *= t ; std::cout << r << '\n' ; // 255.27
    r = r/t ; std::cout << r << '\n' ; // 38.1

    int i = r ; std::cout << i << '\n' ; // 38
}


http://liveworkspace.org/code/1LdIpC$0
Cool, thanks, that's exactly what I wanted. Just for the sake of my understanding can you please clarify how the compiler knows to cast to double and then back to "real" in this:

r += t;
There is no cast back to "real" in r += t; - the type of the expression r += t is 'reference to double'.

s + t is cast back to "real" in r = s + t ; - the type of the expression r = s + t ; is 'reference to real'.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
#include <typeinfo>
#include <cassert>

int main()
{
    real r = 7.8;
    real s = 23.6;
    const real t = 6.7 ;

    {
        // A, B and C are equivalent

        r += t ; // A

        static_cast<double&>(r) += static_cast<double>(t) ; // B

        r.operator double&() += t.operator double() ; // C

        assert( typeid( r += t ) == typeid( double& ) ) ;
    }

    {
        // A, B and  copy-elided(C1,C2,C3,C4) are equivalent

        r = s + t ; // A

        r = real( static_cast<double&>(s) + static_cast<double>(t) ) ; // B

        double& ss = s.operator double&() ; // C1
        double tt = t.operator double() ; // C2
        real rr( ss/tt ) ; // C3
        r = rr ; // C4

        assert( typeid( r = s/t ) == typeid( real& ) ) ;
    }
}
So does the compiler know to cast to reference to double in "A" because it sees that a) there is no "+=" for the real class, b) there is a "+=" operator for the double primitive and c) the real class can implicit cast to double?

So if I had this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct real
{
    real( double v = 0.0 ) : value(v) {}

    // implicit conversions
    operator double& () { return value ; }
    operator double () const { return value ; }
    operator int& () { return value ; }
    operator int () const { return value ; }
    private: double value ;
};

#include <iostream>

int main()
{
    real r = 7.8;
    real s = 23.6;

    r += s;
}


Would the answer be 7.8+23.6 (cast to double) or 7.8+23 (cast to int)? How would it know which one to do, since the "+=" operator exists for both int and double primitives?

Sorry for the million questions, I'm just really trying to understand what's going on...
operator int& () { return value ; } won't compile; int(value) is not a modifiable lvalue.

Just operator int () const is also not a great idea; it would create ambiguities when a const real is involved.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <fstream>

struct real
{
    real( double v = 0.0 ) : value(v) {}

    // implicit conversions
    operator double& () { return value ; }
    operator double () const { return value ; }
    //operator int& () { return value ; } // won't compile
    operator int () const { return value ; }
    private: double value ;
};

int main()
{
    real r = 7.8;
    real s = 23.6;

    r += s; // this is fine; (double&)s

    const real& t = 6.7 ;

    r += t ; // *** error: this is ambiguous; double(t) or int(t)
}


You should not be having conversion operators for any other standard arithmetic type other than double; an implicit conversion from, say, real => int would be still possible, since a user defined conversion and a standard conversion can be combined.

real => int would be real => double (user defined), followed by double => int (standard)
Topic archived. No new replies allowed.