C++: Casting for user defined types

ccastingcompiler-errorscompiler-warningsvisual-studio-2008

How can I get the same handeling of casting for user-defined types as built in, eg:

float a = 5.4;
std::string s = a;//error, no conversion avaible
int x = a;//warning, possible data loss
int y = (int)a;//fine
int z = static_cast<int>a;//fine
float b = c;//warning, possible data loss

Now say I have my own Int and Float class, how do I go about getting the same errors and warnings?

class Int
{
public:
    int value;
    Int(int v);
    ...
};
class Float
{
public:
    float value;
    Float(float v);
    ...
};
Int c = 10;
Float a = 5.5;
std::string s = a;
Int x = a;
Int y = (Int)a;
Int z = static_cast<Int>a;
Float b = c;

I'm aware of creating overloaded cast operators, and using constructors, however I don't know how to make this work correctly for implicit and explicit casts, eg consider. If I dont add explicit casts within those methods, then I get a warning when there compiled but not when their call, and if I do, then I don't get an error within the classes code, but I still don't get a warning when there used either.

I'm guessing there is some way as to mark the cast operator as explicit, so that a warning is generated if it tries to cast implicitly, but not with explicit (either C-Style or static_cast) casts)

EDIT:
Ok I think I get it for cases like this where all types in question are fully, known, but what about times when one or both are templates, and that neither types map onto a built-in type?

template<typename T> class Vector2
{
public:
    T x, y;
    Vector2():x(0),y(0){}
    Vector2(T x, T y):x(x),y(y){}

    //Works as expected, warning if adding T and T2 is unsafe, or error if
    //incompatible*/
    template<typename T2>Vector2<T>& operator += (const Vector2<T2> &v);
    //Also works as desired
    Vector2<T>& operator *= (T s);

    //allows the conversion, but always generates warnings if 
    //T and T2 would, even if this constructor is used by an explicit
    //case. How can I suppress the warnings for the explicit cast, but
    //not for implicit casts?
    template<typename T2>Vector2(const Vector2<T2> &v);//uses implicit conversion form T2 to T
};

An implicit cast from say Vector2 to Vector2 works as expected, but the cast from say Vector2 to Vector2 always causes (2, one for x and one for y) warnings, even if a explicit C-Style or static_cast was used. I want to keep the warnings for the implicit cast, but not the explicit casts.

I know I could hack around this be creating a special T vector_cast(T2) type method that uses explicit casts for each element internally, but Id rather be able to use the C-Style and static_casts

Best Answer

I don't think there's a way, either. The best I could achieve is so that the line that you want to generate a warning doesn't compile at all.

class Int
{
public:
    int value;
    Int(int v);
};

class Float
{
public:
    float value;
    Float(float v);
    operator int() { return static_cast<int>(value); }
};

int main()
{
    Float a = 5.5;
    //Int x = a; //no warning, simply doesn't compile
    Int y = (int)a;
    Int z = static_cast<int>(a);
}

Edit: regarding your question about Vector2

One thing to do might be to disable all implicit conversions between different Vector2 types. As a short-cut you might provide a vector_cast to allow explicit conversions:

template <class T, class S>
Vector2<T> vector_cast(const Vector2<S>& s)
{
    return Vector2<T>(static_cast<T>(s.x), static_cast<T>(s.y));
}

Another thing might be to bring in some template metaprogramming, to enable conversion constructor for safe conversions.

It seems to me that boost doesn't contain such a type_trait, hence I rolled my own.

It is somewhat simplified: Target must be at least as large as Source, and Target must not be integral if Source is floating point. However, it disregards issues of signedness, and the question whether a floating-point type can represent the full range of an integer type (e.g float cannot store all 32-bit ints precisely, but double can).

#include <boost/type_traits.hpp>
#include <boost/utility/enable_if.hpp>

template <class S, class T>
struct is_safe_conversion:
    boost::integral_constant<
        bool,
        (sizeof(S) <= sizeof(T)) && !(boost::is_floating_point<S>::value && boost::is_integral<T>::value)
    >
{
};

template<typename T> class Vector2
{
public:
    T x, y;
    Vector2():x(0),y(0){}
    Vector2(T x, T y):x(x),y(y){}

    template <class U>
    Vector2(const Vector2<U>& other, typename boost::enable_if<is_safe_conversion<U, T> >::type* = 0):
        x(other.x), y(other.y) {}

};

template <class T, class S>
Vector2<T> vector_cast(const Vector2<S>& s)
{
    return Vector2<T>(static_cast<T>(s.x), static_cast<T>(s.y));
}

int main()
{
    Vector2<double> vd, vd2;
    Vector2<int> vi, vi2;
    Vector2<float> vf, vf2;

    vd = vd2;
    vd = vi;
    vd = vf;

    //vi = vd; //error
    vi = vector_cast<int>(vd);
    vi = vi2;
    //vi = vf; //error
    vi = vector_cast<int>(vf); //explicit

    //vf = vd; //error
    vf = vector_cast<float>(vd);

    //following compiles, but produces a warning (float cannot represent all integers) 
    //TODO: enhance is_safe_conversion!
    vf = vi; 
    vf = vf2;
}