Custom type range/value check compile-time, real-time

Viewed 157

When we define a variable of a built-in type too large to parse, Compiler/IDE (I'm using Visual Studio 2019) warns right away that the range has been violated, even before compiling the project - "integer constant is too large" if we're defining an integer.

int sum = 100000000000000000000; // E0023, "integer constant is too large"

Is it possible to achieve similar type checking for user defined types in c++/VS (I'm not sure if this is purely IDE constrained, might be though - I'm new to c++)? Would be helpful to tell about going out of range in some cases, e.g.

MonthDay day = 32; // error, "MonthDay literal can't be greater than 31."
Weight applesWeight = 2900000_kg; // "Item too heavy." Yet better if it could also work with custom literals.

A version from constexpr constructor with compile time validation uses constexpr, but that requires the keyword at variable declaration.

class A
{
public:
    constexpr A(int i) : i(i != 42 ? throw 42 : i) {}
private:
    int i;
};

// usage
constexpr A ab = 43; // error, "Expression must have a constant value"
constexpr A abc = 42; // ok

=== Outcome ===

As Lev.M. mentions in the comments this type of quick 'pre-compile-time' analyses are done with plug-ins (sometimes called a linter, and they're do more basic checks than other static analyzers - link below) and apparently VC++ 2019 comes with a new linter built-in. That's where native type overflows are checked as code is being typed, and it seems there's no way to extend it at this point for VC++ (has been possible for C# for some while now, if you're familiar with Visual Studio, Roslyn, etc.) I was wondering if the new VC++ linter could've been extended somehow, but looks like not currently. Marking @cigien's post as the answer.

https://devblogs.microsoft.com/cppblog/intellisense-code-linter-for-cpp/

1 Answers

You can add a check to your constructor for the argument being inside a range, and then assert on that:

struct MonthDay
{
  constexpr MonthDay (int data) : data(data)
  {
    if (data > 31)
    {
        assert(false); // months can't have more than 31 days
    }  
  }

 private:
  int data;
};

So long as the constructor is constexpr this works fine with user-defined-literals:

constexpr MonthDay operator "" _md ( unsigned long long arg )
{ 
  return MonthDay (arg);
}

and now you can get a nice error when the initialization fails:

MonthDay a = 15_md;  // ok
MonthDay b = 32_md;  // run-time error

This also gives a compile time error if initialized with an out-of-range value at compile-time, because of the assert being evaluated in the if clause of the constructor:

constexpr MonthDay c = 15_md; // ok
constexpr MonthDay d = 32_md; // compile time error

Here's a demo

Related