How can I range check C++14 user defined literals?

Viewed 317

Is there a way to persuade a good C++ compiler to warn about the overflow in constructing a user defined type from a user defined literal as shown in the example code below? At least (via Godbolt) GCC and Clang warn about the overflow in the equivalent intrinsic code, but MSVC doesn't. None care about the same error in the user defined code.

#include <iostream>

struct MyShort
{
  short  data;
  constexpr MyShort ( short arg ) : data ( arg ) {}
};

constexpr MyShort operator "" _MyShort ( unsigned long long arg )
{
  return MyShort ( arg );
}

struct UseMyShort
{
  MyShort constexpr static  var1 = 100000_MyShort;
  short constexpr static    var2 = 100000;
};

int main ( int argc, char** argv )
{
  std::cout << UseMyShort::var1.data;
  std::cout << UseMyShort::var2;
}

UPDATE: Thanks pyj and cigien. You gave me the clue. When the code is run at compile time, the compiler doesn't care about branches not taken, so non-constexpr expressions are fine. If the compiler executes that path it errors which is the desired outcome. I have adapted your solutions to assert so the code also detects runtime errors when in debug mode. Updated code below. I've accepted cigien's answer because it looks like they took a penalty for a faulty draft, not a faulty idea.

#include <iostream>
#include <limits>
#include <assert.h>

template <class elem>
constexpr elem rangeCheck ( unsigned long long arg )
{
  if ( arg > std::numeric_limits<elem>::max() ) {
    assert ( false );
  }
  return elem(arg);
}

struct MyShort
{
  short  data;
  constexpr MyShort ( short arg ) : data ( arg ) {}
};

constexpr MyShort operator "" _MyShort ( unsigned long long arg )
{
  return MyShort { rangeCheck<short> (arg) };
}

struct UseMyShort
{
// compile time error (GCC):-
// ... call to non-constexpr function ‘void __assert_fail(const char*, const char*, unsigned int, const char*)’
//     assert ( false );
//     ^
  MyShort constexpr static  var1 = 100000_MyShort;
  short constexpr static    var2 = 100000; // compile time warning (with GCC -Woverflow)
};

int main ( int, char** )
{
  std::cout << UseMyShort::var1.data;
  std::cout << UseMyShort::var2;
  MyShort const var4 = 100000_MyShort; // runtime error
  MyShort       var5 = 100000_MyShort; // runtime error  
  return 0;
}

3 Answers

You should short-circuit your test against an exception throw, which is an expression and cannot be constexpr. When you pass in a value which doesn't pass this test, the compiler sees an expression, when you pass in an acceptable value, it sees a constexpr value.

#include <exception>
#include <iostream>
#include <limits>

struct MyShort
{
    short  data;
    constexpr MyShort(const short arg) : data(arg) {}
};

constexpr MyShort operator "" _MyShort(const unsigned long long arg)
{
    return (arg > std::numeric_limits<short>::max()) ? throw std::exception() : static_cast<short>(arg);
}

struct UseMyShort
{
    MyShort constexpr static  var1 = 1000_MyShort;
    short constexpr static    var2 = 100000;
};

int main ( int argc, char** argv )
{
  std::cout << UseMyShort::var1.data;
  std::cout << UseMyShort::var2;
}

References: Andrzej's C++ blog describes this:

You can force a hard error by checking if the unsigned long long's value is narrowed when cast to a short value:

constexpr MyShort operator "" _MyShort ( unsigned long long arg )
{
  if (static_cast<short>(arg) != arg)
  {
    std::cout << "oops";  // or some other non-constexpr code
  }  
  return MyShort (arg);
}

Here's a demo.

You can initialize MyShort with a list-initialization: narrowing conversion are forbidden in list-initialization. All compilers detect the error (MSVC included)

Live Demo

constexpr auto pow10 (unsigned long long i){
     if (i==0) return 1uLL;
     else return 10uLL * pow10(i-1);
}

template <char...cs, size_t...Is>
constexpr auto do_MyShort ( std::index_sequence <Is...> )
{
  return MyShort{
      ((pow10(sizeof...(Is) - Is - 1)*(cs-'0')) + ...)
  };
}

template <char...cs>
constexpr MyShort operator "" _MyShort (  )
{
  return do_MyShort <cs...> (std::make_index_sequence <sizeof...(cs)>{});
}
Related