Search code examples
c++c++11type-safety

Type safety during assignment vs initialization


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?


Solution

  • 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.