如何对C++14用户定义字面量进行范围检查?

8
有没有办法说服一个好的C++编译器警告关于从用户定义字面量构造用户定义类型中的溢出,如下所示的示例代码?至少(通过Godbolt)GCC和Clang警告相等内部代码中的溢出,但MSVC不会。没有人关心用户定义代码中的同样错误。
#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;
}

更新:感谢pyj和cigien的提示。当代码在编译时运行时,编译器不关心未执行的分支,因此非constexpr表达式是可以接受的。如果编译器执行了该路径,则会出现错误,这是期望的结果。我已经根据你们的解决方案进行了适应性修改,以便在调试模式下也能检测到运行时错误。下面是更新后的代码。我接受了cigien的答案,因为他们似乎承担了一个有缺陷的草稿的惩罚,而不是有缺陷的想法。

#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个回答

2

您应该在异常抛出时对测试进行短路处理,异常是表达式,不能是constexpr。当您传入一个不通过此测试的值时,编译器将看到一个表达式,而当您传入一个可接受的值时,它将看到一个constexpr值。

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

参考资料: Andrzej的C++博客对此进行了描述:


1

如果将 unsigned long long 值强制转换为 short 值时发生了缩小,您可以通过检查来强制引发一个硬错误:

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

这里是一个演示


@Evg 啊,遗憾的是,我不确定现有的踩票是否有影响,但这个答案没有赞同票,而这与其他发布的答案相同 :( - cigien

0

你可以使用列表初始化方式初始化MyShort:缩窄转换在列表初始化中是被禁止的。所有编译器都会检测到这个错误(包括MSVC)

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接