9

I would like to create a type that is an integer value, but with a restricted range. Attempting to create an instance of this type with a value outside the allowable range should cause a compile time error.

I have found examples that allow compile time errors to be triggered when an enumeration value outside those specified is used, but none that allow a restricted range of integers (without names).

Is this possible?

  • Boost has a static assert for numerical relations: http://www.boost.org/doc/libs/1_37_0/libs/mpl/doc/refmanual/assert-relation.html – In silico Sep 12 '10 at 20:04
  • @In silico: please try and post links to the new version of boost (1.44 as of writing) :) – Matthieu M. Sep 13 '10 at 08:46
  • I think that Clang already have some kind of diagnostic, at compile-time, when assigning from a large numeric type to a smaller one. I suppose gcc / visual would have as well. Would it be sufficient or do you want well delimited ranges ? – Matthieu M. Sep 13 '10 at 09:14
  • @Matthieu M: I was looking for ranges such as 0-100 or 1-50. –  Sep 14 '10 at 17:33
  • in this case you'll have to use something similar to what @Motti proposed :) – Matthieu M. Sep 14 '10 at 18:53

4 Answers4

8

Yes but it's clunky:

// Defining as template but the main class can have the range hard-coded
template <int Min, int Max>
class limited_int {
private:
    limited_int(int i) : value_(i) {}
    int value_; 
public:
    template <int Val> // This needs to be a template for compile time errors
    static limited_int make_limited() { 
        static_assert(Val >= Min && Val <= Max, "Bad! Bad value.");
        // If you don't have static_assert upgrade your compiler or use:
        //typedef char assert_in_range[Val >= Min && Val <= Max];
        return Val;
    }

    int value() const { return value_; }
};

typedef limited_int<0, 9> digit;
int main(int argc, const char**) 
{

    // Error can't create directly (ctor is private)
    //digit d0 = 5; 

    // OK
    digit d1 = digit::make_limited<5>(); 

    // Compilation error, out of range (can't create zero sized array)
    //digit d2 = digit::make_limited<10>(); 

    // Error, can't determine at compile time if argc is in range
    //digit d3 = digit::make_limited<argc>(); 
}

Things will be much easier when C++0x is out with constexpr, static_assert and user defined literals.

Motti
  • 110,860
  • 49
  • 189
  • 262
  • Nice idea with the array 0 or 1 sized. I used the regular enable_if technique. – Puppy Sep 12 '10 at 20:27
  • This is great - To prevent a compiler warning over unused assert_in_range variable I have added " = {}" to that line. –  Sep 12 '10 at 20:29
  • @DeadMG, this is the traditional way to do it, I used to use `enable_if` for its template coolness but came to realise it's overkill (`static_assert` is a welcome addition to the language) – Motti Sep 12 '10 at 20:49
  • @Andrew, actually it's better to just use a typedef (I was lazy when writing the answer) I'll update my answer. – Motti Sep 12 '10 at 20:52
  • What would an answer look like in C++20? – Prof. Falken Aug 04 '22 at 00:39
  • 1
    @Prof.Falken, good question, probably much cleaner. I haven't been using C++ lately so I'm not sure exactly. I would be interested to see what someone can come up with. – Motti Aug 04 '22 at 06:34
4

Might be able to do something similar by combining macros and C++0x's static assert.

#define SET_CHECK(a,b) { static_assert(b>3 && b<7); a=b; }
Philipp
  • 48,066
  • 12
  • 84
  • 109
  • 4
    Now that's horrid :/ Please refrain from defining macros when the situation certainly doesn't call for it, a template function would be much better. – Matthieu M. Sep 13 '10 at 08:47
1

A runtime integer's value can only be checked at runtime, since it only exists at runtime, but if you make a runtime check on all writing methods, you can guarantee it's contents. You can build a regular integral replacement class with given restrictions for that.

For constant integers, you could use a template to enforce such a thing.

template<bool cond, typename truetype> struct enable_if {
};
template<typename truetype> struct enable_if<true, truetype> {
    typedef truetype type;
};
class RestrictedInt {
    int value;
    RestrictedInt(int N)
        : value(N) {
    }
public:
    template<int N> static typename enable_if< (N > lowerbound) && (N < upperbound), RestrictedInt>::type Create() {
        return RestrictedInt(N);
    }
};

Attempting to create this class with a template value that isn't within the range will cause a substitution failure and a compile-time error. Of course, it will still require adornment with operators et al to replace int, and if you want to compile-time guarantee other operations, you will have to provide static functions for them (there are easier ways to guarantee compile-time arithmetic).

Puppy
  • 144,682
  • 38
  • 256
  • 465
0

Well, as you noticed, there is already a form of diagnostic for enumerations.

It's generally crude: ie the checking is "loose", but could provide a crude form of check as well.

enum Range { Min = 0, Max = 31 };

You can generally assign (without complaint) any values between the minimal and maximal values defined.

You can in fact often assign a bit more (I think gcc works with powers of 2).

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722