6
\$\begingroup\$

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

Toby Speight
87.1k14 gold badges104 silver badges322 bronze badges
asked Oct 27, 2019 at 3:41
\$\endgroup\$
1
  • \$\begingroup\$ You could make use of the curly-brace style initialization, which already disallows narrowing conversions. \$\endgroup\$ Commented Jun 21, 2021 at 15:43

1 Answer 1

2
\$\begingroup\$

I've attempted this before, and it's a little more complicated than checking if the value round-trips correctly. Consider:

int x = -3;
unsigned int y = narrow_cast<unsigned int>(x);

This passes the round-trip test, but y has a different value than x. Before you know it, you'll be writing a lot of if constexpr with type traits in order to try to match the narrowing rules.

The Guidelines Standard Library has a narrow_cast that's explored in Understanding gsl::narrow implementation on Stack Overflow.

Toby Speight
87.1k14 gold badges104 silver badges322 bronze badges
answered Jun 18, 2021 at 22:21
\$\endgroup\$
0

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.