Rouki
Rouki

Reputation: 2355

Using unsigned int vs. checking if negative

Say I have a class with an integer that should be always positive with a simple C'tor:

class A {
    unsigned int x;
    public:
    A(unsigned int X) : x(X) {}
};

And lets say someone accidentally creates an A object with the value -5. Of course, this is not valid and X will get a really huge value since the MSB now doesnt stand for the number sign. Problem is, that now I can't really check if the number is negative or invalid (maybe bitwise?).

Should I just avoid the unsigned and just use a plain int? By doing so, i could just throw an exception if the given value is beyond a max value OR below zero.

Would love to hear some suggestions.

Upvotes: 24

Views: 4753

Answers (3)

sehe
sehe

Reputation: 393934

Two approaches come to mind:

  1. Add an explicit conversion for the 'signed' types.

    #include <cassert>
    
    class A {
        unsigned int x;
        public:
        A(unsigned int X) : x(X) {}
        explicit A(int X) : x(static_cast<unsigned>(X)) {
            assert(X>=0); // note X, not x!
        }
    };
    
    int main()
    {
        A ok(5);
        A bad(-5);
    }
    
  2. Prohibit implicit conversions by deleting better overloads:

    A(int X) = delete;
    A(long X) = delete;
    A(char X) = delete;
    

    This will require all users to cast to unsigned before constructing the A instance. It's safe but clumsy.

Note that this does not prohibit implicit conversions from all integral types (e.g. enums) so you'd need to do more to make this fool proof.

Here is a rudimentary SFINAE-based example that accepts all implicit conversions except if they involve signed values: Live on Coliru

#include <type_traits>
#include <limits>

class A {
    unsigned int x;
    public:
    template<typename T, typename = typename std::enable_if<std::is_integral<T>::value, void>::type>
    A(T X) : x(X)
    {
        static_assert(!std::numeric_limits<T>::is_signed, "Signed types cannot be accepted");
    }
};

int main()
{
    A ok(5u);
    A bad(-5);
}

Upvotes: 37

user2288008
user2288008

Reputation:

I probably agree with DanielKO about pattern will pop up everywhere. And note that sehe SFINAE-based solution don't work for uint64 -> uint32 truncation. So my answer:

class A {
 public:
  using value_type = unsigned int;

  template <class T>
  explicit A(T x): x_(boost::numeric_cast<value_type>(x)) { // can be check in debug only
    static_assert(std::is_integral<T>::value, "T must be integral");
  }

 private:
  value_type x_;
};

And live example.

Upvotes: 1

Lundin
Lundin

Reputation: 215305

And lets say someone accidentally creates an A object with the value -5

While it may be good practice to make your program robust enough to accept such errors, the root cause of the bug is a sloppy programmer who does not have sufficient warnings enabled.

To get to the bottom of the problem, you need to ensure that the code is compiled with all warnings enabled, and perhaps also consider using an external static analysis tool.

Upvotes: 7

Related Questions