To summarize what I've been trying to do, I basically tried to make a safe narrow_cast
operator, which casts to the Target type if and only if the value is unchanged (not narrowed) in the process, and throws an exception if the conversation will cause the value to change.
So I implemented this by making two templates, one for when std::numeric_limits
of the Target type is specialized (by using std::numeric_limits<T>::is_specialized
in conjunction with std::enable_if
), and check if base is higher than the max value of target type of lower than the min value of target type, if it is, then throw an exception, elsewise, just do a static cast to the target.
And the second for when the numeric_limits
are not specialized, by basically casting to the Target type, then casting back to the Base, and then comparing the old value with the new one. If both are the same, just return the Target casted value, if not, throw an exception.
But then, I got a response from the comments that my code might cause problems when Integer and Floating Point types are mixed, as even though a Floating point might fit inside an Integer range, it can still not be completely expressed by that type, so I stuck to ALWAYS using the second alternative.
Old Code [BAD, doesn't work properly when Integer and Floating Point types are mixed]
// utils.hh
// Contains globally used utility functions, templates, using declarations, etc.
#ifndef PHI_SRC_UTILS_HH
#define PHI_SRC_UTILS_HH
#include <string>
#include <vector>
#include <limits>
template <typename T>
using Vector = std::vector<T>;
using String = std::string;
using Size = std::size_t;
class bad_narrow_cast : public std::bad_cast
{
public:
bad_narrow_cast(const char* message) : what_(message)
{
}
char const *what() { return what_; }
private:
char const *what_;
};
template <typename Target, typename Base> static inline
typename std::enable_if<std::numeric_limits<Target>::is_specialized,
Target>::type narrow_cast(Base const &base)
{
if(base > static_cast<Base>(std::numeric_limits<Target>::max()) ||
base < static_cast<Base>(std::numeric_limits<Target>::min()))
{
throw(bad_narrow_cast((String() + "Invalid narrowing conversation from type " +
typeid(Target).name() + " to type " + typeid(Base).name()).c_str()));
}
return static_cast<Target>(base);
}
template <typename Target, typename Base> static inline
typename std::enable_if<!std::numeric_limits<Target>::is_specialized,
Target>::type narrow_cast(Base const &base)
{
Target target = static_cast<Target>(base);
Base narrowed_base = static_cast<Base>(target);
if (base == narrowed_base)
return target;
throw(bad_narrow_cast((String() + "Invalid narrowing conversation from type " +
typeid(Target).name() + " to type " + typeid(Base).name()).c_str()));
}
#endif
Fixed Code:
// utils.hh
// Contains globally used utility functions, templates, using declarations, etc.
#ifndef PHI_SRC_UTILS_HH
#define PHI_SRC_UTILS_HH
#include <string>
#include <vector>
#define UNUSED(x) (void)x
template <typename T>
using Vector = std::vector<T>;
using String = std::string;
using Size = std::size_t;
class bad_narrow_cast : public std::bad_cast
{
public:
bad_narrow_cast(char const *message) : what_(message)
{
}
char const *what() { return what_; }
private:
char const *what_;
};
template <typename Target, typename Base> static inline
Target narrow_cast(Base const &base)
{
Target target = static_cast<Target>(base);
Base narrowed_base = static_cast<Base>(target);
if (base == narrowed_base)
return target;
throw(bad_narrow_cast((String() + "Invalid narrowing conversation from type " +
typeid(Target).name() + " to type " + typeid(Base).name()).c_str()));
}
#endif
But I'm still unsure if I'm doing things the right way, so, comments on the code is appreciated, it'd be really helpful to improve it more if possible