Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c++

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;
}

like image 435
Pete D. Avatar asked Jun 01 '20 16:06

Pete D.


People also ask

What is user-defined literal in c++?

A literal is used for representing a fixed value in a program. A literal could be anything in a code like a, b, c2. , 'ACB', etc. Similarly, User-Defined Literals (UDL) provides literals for a variety of built-in types that are limited to integer, character, floating-point, string, boolean, and pointer.

What is a user-defined literal?

In source code, any literal, whether user-defined or not, is essentially a sequence of alphanumeric characters, such as 101 , or 54.7 , or "hello" or true . The compiler interprets the sequence as an integer, float, const char* string, and so on.


2 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:

  • User-defined literals — Part I
  • Compile-time computations
like image 189
pyj Avatar answered Oct 13 '22 00:10

pyj


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.

like image 22
cigien Avatar answered Oct 12 '22 22:10

cigien