【问题标题】:Forbid integer conversion with precision loss禁止精度损失的整数转换
【发布时间】:2017-07-24 14:32:42
【问题描述】:

如何防止此类代码编译?

#include <vector>
#include <limits>
#include <iostream>
#include <cstdint>

int main() {
  std::vector<int16_t> v;
  v.emplace_back(std::numeric_limits<uint64_t>::max());
  std::cout << v.back() << std::endl;
  return 0;
}

g++ 和 -std=c++14 -Wall -Wextra -Werror -pedantic -Wold-style-cast -Wconversion -Wsign-conversion 甚至不会发出警告。该示例还可以使用std::vector&lt;uint16_t&gt; 编译而没有警告

【问题讨论】:

  • 我认为,如果您想要任何形式的安全性,请停止使用该语言的默认整数。使用安全的数值库

标签: c++ c++11 type-conversion implicit-conversion compiler-flags


【解决方案1】:

-Wsystem-headers 添加到命令行。在众多虚假警告中,您会找到想要的警告。

In file included from (...)include/c++/6.3.0/x86_64-w64-mingw32/bits/c++allocator.h:33:0,
                 from (...)include/c++/6.3.0/bits/allocator.h:46,
                 from (...)include/c++/6.3.0/vector:61,
                 from test.cpp:1:
(...)include/c++/6.3.0/ext/new_allocator.h: In instantiation of 'void __gnu_cxx::new_allocator<_Tp>::construct(_Up*, _Args&& ...) [with _Up = short int; _Args = {long long unsigned int}; _Tp = short int]':
(...)include/c++/6.3.0/bits/alloc_traits.h:455:4:   required from 'static void std::allocator_traits<std::allocator<_Tp1> >::construct(std::allocator_traits<std::allocator<_Tp1> >::allocator_type&, _Up*, _Args&& ...) [with _Up = short int; _Args = {long long unsigned int}; _Tp = short int; std::allocator_traits<std::allocator<_Tp1> >::allocator_type = std::allocator<short int>]'
(...)include/c++/6.3.0/bits/vector.tcc:96:30:   required from 'void std::vector<_Tp, _Alloc>::emplace_back(_Args&& ...) [with _Args = {long long unsigned int}; _Tp = short int; _Alloc = std::allocator<short int>]'
test.cpp:9:54:   required from here
(...)include/c++/6.3.0/ext/new_allocator.h:120:4: error: conversion to 'short int' from 'long long unsigned int' may alter its value [-Werror=conversion]
  { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
    ^

我知道这不是一个真正的解决方案,尽管它在技术上回答了这个问题。

问题在于,emplace_back 将所有参数(在本例中为 uint64_t)转发给包含类型的构造函数。首先emplace_back 的参数被推断为uint64_t。在 emplace 返回时不会发生转换。然后,缩小转换发生在系统标头中的 emplace_back 实现“内部”。编译器不知道这是调用者的错,并抑制警告,因为它在系统头文件中。

【讨论】:

    【解决方案2】:

    我通过模板和专业化解决了这个问题:

     template<
            typename T/*the desired type*/,
            typename Y/*the source type*/
        > T integral_cast(const Y& y)
        {
            static_assert(false, "undefined integral_cast");
        }
    

    如果我想让演员工作,我会专注于休闲:

    // Pass through for uint32_t
        template<>
        inline std::uint32_t integral_cast(const uint32_t& y)
        {
            return y;
        }
    

    // Specialisation to convert std::uint32_t to double
        template<>
        inline double integral_cast(const std::uint32_t& y)
        {
            double ret = static_cast<double>(y); // this never loses precision under IEEE754
            return ret;
        }
    

    在使用时,您编写表单代码

    int16_t y = integral_cast<int16_t>(std::numeric_limits<uint64_t>::max());
    

    【讨论】:

    • 为什么要引用输入?这会强制在输入上使用 ODR,这会强制调用者对其常量进行定义(对于类静态常量可以省略)。此外,由于别名分析“保守主义”,它可能会使运行时变得悲观。我认为在这里复制值不是问题
    • 如果你删除了const 引用,你会冒在调用站点进行类型转换的风险,这可能是不可取的。不过基本上是程序员的选择。
    【解决方案3】:

    您可以开始为您的整数类型编写一个包装器以匹配确切的类型(或某些条件)。

    #include <cstdint>
    #include <iostream>
    #include <limits>
    #include <type_traits>
    #include <vector>
    
    template <class...> struct conjunction : std::true_type {};
    template <class B1> struct conjunction<B1> : B1 {};
    template <class B1, class... Bn>
    struct conjunction<B1, Bn...>
        : std::conditional_t<bool(B1::value), conjunction<Bn...>, B1> {};
    
    template <typename T> struct int_wrapper {
      explicit int_wrapper() : _val{T{}} {}
    
      explicit int_wrapper(const int_wrapper &other) : _val{other._val} {}
    
      template <typename U> explicit int_wrapper(U val) : _val{val} {
        static_assert(sizeof(T) >= sizeof(U), "Size mismatch.");
        static_assert(conjunction<std::is_signed<T>, std::is_signed<U>>::value,
                      "sign mismatch");
      }
    
      explicit operator T() { return _val; }
      explicit operator T() const { return _val; }
    
      T _val;
    };
    
    std::ostream &operator<<(std::ostream &stream, const int_wrapper<int16_t> &v) {
      stream << v._val;
      return stream;
    }
    
    int main() {
      std::vector<int_wrapper<int16_t>> v;
      v.emplace_back(std::numeric_limits<uint64_t>::max());
      std::cout << v.back() << std::endl;
    
      return 0;
    }
    

    macOS 上的clang 会给你类似的错误:

    so.cpp:18:60: error: non-constant-expression cannot be narrowed from type 'unsigned long long' to 'short' in initializer list
          [-Wc++11-narrowing]
      template <typename U> explicit int_wrapper(U val) : _val{val} {
                                                               ^~~
    /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/memory:1752:31: note: in
          instantiation of function template specialization 'int_wrapper<short>::int_wrapper<unsigned long long>' requested here
                ::new((void*)__p) _Up(_VSTD::forward<_Args>(__args)...);
                                  ^
    /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/memory:1668:18: note: in
          instantiation of function template specialization 'std::__1::allocator<int_wrapper<short> >::construct<int_wrapper<short>,
          unsigned long long>' requested here
                {__a.construct(__p, _VSTD::forward<_Args>(__args)...);}
                     ^
    /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/memory:1514:14: note: in
          instantiation of function template specialization 'std::__1::allocator_traits<std::__1::allocator<int_wrapper<short> >
          >::__construct<int_wrapper<short>, unsigned long long>' requested here
                {__construct(__has_construct<allocator_type, _Tp*, _Args...>(),
                 ^
    /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/vector:1643:25: note: in
          instantiation of function template specialization 'std::__1::allocator_traits<std::__1::allocator<int_wrapper<short> >
          >::construct<int_wrapper<short>, unsigned long long>' requested here
            __alloc_traits::construct(this->__alloc(),
                            ^
    so.cpp:37:5: note: in instantiation of function template specialization 'std::__1::vector<int_wrapper<short>,
          std::__1::allocator<int_wrapper<short> > >::emplace_back<unsigned long long>' requested here
      v.emplace_back(std::numeric_limits<uint64_t>::max());
        ^
    so.cpp:18:60: note: insert an explicit cast to silence this issue
      template <typename U> explicit int_wrapper(U val) : _val{val} {
                                                               ^~~
                                                               static_cast<short>( )
    so.cpp:19:5: error: static_assert failed "Not the same size."
        static_assert(sizeof(T) >= sizeof(U), "Not the same size.");
        ^             ~~~~~~~~~~~~~~~~~~~~~~
    so.cpp:20:5: error: static_assert failed "sign mismatch"
        static_assert(conjunction<std::is_signed<T>, std::is_signed<U>>::value,
        ^             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    3 errors generated.
    

    【讨论】:

      猜你喜欢
      • 2018-08-02
      • 1970-01-01
      • 2019-03-06
      • 2015-06-28
      • 2013-07-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多