Precision Loss Prefix Integer Conversion

How to prevent compilation of such code?

#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 ++ and clang with -std=c++14 -Wall -Wextra -Werror -pedantic -Wold-style-cast -Wconversion -Wsign-conversion do not even warn about this. The example also compiles without warning using std::vector<uint16_t>

+7
c ++ type-conversion c ++ 11 implicit-conversion compiler-flags
source share
3 answers

Add -Wsystem-headers to the command line. Among the many false warnings you will find the desired warning.

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

I know this is not a real solution, although it technically answers the question.

The problem is that emplace_back forwards all arguments, in this case uint64_t to the constructor of the contained type. The emplace_back argument is emplace_back output as uint64_t . Conversion does not occur when emplace back is called. Then the narrowing of the conversion occurs β€œinside” the emplace_back implementation in the system header. The compiler does not know that this is the error of the caller and suppresses the warning, because it is in the system header.

+2
source share

I solve this by templates and specialization:

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

which I then specialize in leisure, if I want the cast to work:

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

and

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

At the time of use, you write the form code

 int16_t y = integral_cast<int16_t>(std::numeric_limits<uint64_t>::max()); 
+1
source share

You can start writing a wrapper for your integral types according to the exact type (or some conditions).

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

clang on macOS gives you an error, for example:

 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. 
0
source share

All Articles