One of the stated advantages of initializer list syntax is that it improves type safety by prohibiting narrowing conversions:
int x {7.9}; // error: narrowing
int y = 7.9; // OK: y becomes 7. Hope for a compiler warning
However, AFAIK there is no way to enforce the same check in a subsequent assignment:
int z {7};
z = 7.9; // allows narrowing, and no other syntax available
Why is type safety during initialization given greater attention by the language design than type safety during assignment? Is narrowing in assignments less likely to cause bugs and/or harder to detect?
If x
is an int
variable, then
x = 7.9;
must continue working in C++11 and later for the reason of backward compatibility. This is the case even if x
was brace-initialized. A brace-initialized int
variable has the same type as a non-brace-initialized int
variable. If you passed x
by reference to a function,
void f(int& r);
f(x);
then in the body of f
there would be no way to "tell" whether the object had been brace-initialized, so the language cannot apply different rules to it.
You can prevent narrowing conversions yourself by doing this:
x = {7.9}; // error
You could also try to take advantage of the type system by creating an int
"wrapper" type that disallowed narrowing assignments:
struct safe_int {
template <class T> safe_int(T&& x) : value{std::forward<T>(x)} {}
template <class T> safe_int& operator=(T&& x) {
value = {std::forward<T>(x)}; return *this;
}
operator int() const { return value; }
int value;
};
Unfortunately this class cannot be made to behave like an actual int
object under all circumstances.