Search code examples
c++visual-c++linter

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


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/


Solution

  • 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