How to prevent such code from compiling?
#include
#include
#include
#include
int main() {
std::
I solve this by templates and specialisation:
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 specialise at 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 point of use you write code of the form
int16_t y = integral_cast<int16_t>(std::numeric_limits<uint64_t>::max());
You could start writing a wrapper for your integral types to match 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 error like:
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.
Add -Wsystem-headers
to the command line. Among the many spurious 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 the arguments, in this case the uint64_t
to the constructor of the contained type. First the argument to emplace_back
is deduced as uint64_t
. No conversion happens at the call of emplace back. The narrowing conversion then happens "inside" the emplace_back
implementation in the system header. The compiler does not know that this is the fault of the caller and suppresses the warning because it is in a system header.