Как предотвратить неявное преобразование из int в unsigned int?


Предположим, что у вас есть это:

struct Foo {
    Foo(unsigned int x) : x(x) {}
    unsigned int x;
};

int main() {
    Foo f = Foo(-1);     // how to get a compiler error here?
    std::cout << f.x << std::endl;
}

Можно ли предотвратить неявное преобразование?

Единственный способ, который я мог бы придумать, - это явно предоставить конструктор, который принимает int и генерирует какую-то ошибку времени выполнения, если int отрицателен, но было бы лучше, если бы я мог получить ошибку компилятора для этого.

Я почти уверен, что есть дубликат, но ближе всего я смог найти этот вопрос , который скорее спрашивает, почему неявный конвертация разрешена.

Меня интересуют оба решения, C++11 и pre C++11, предпочтительно тот, который будет работать в обоих.

3 30

3 ответа:

Равномерная инициализация предотвращает сужение.

Он следует примеру (не работает, как и было предложено):

struct Foo {
    explicit Foo(unsigned int x) : x(x) {}
    unsigned int x;
};

int main() {
    Foo f = Foo{-1};
    std::cout << f.x << std::endl;
}

Просто привыкайте использовать единообразную инициализацию (Foo{-1} вместо Foo(-1)) везде, где это возможно.

EDIT

В качестве альтернативы, согласно запросу OP в комментариях, решение, которое работает также с C++98, должно объявить как private конструкторы, получающие int (long int, и так далее).
На самом деле нет необходимости их определять.
Замечание это = delete было бы также хорошим решением, как предложено в другом ответе, но это тоже начиная с C++11.

EDIT 2

Я хотел бы добавить еще одно решение, хотя оно действительно с C++11.
Идея основана на предложении Voo (смотрите комментарии ответа Брайана для более подробной информации) и использует SFINAE на аргументах конструктора.
Он следует минимальному, рабочему примеру:

#include<type_traits>

struct S {
    template<class T, typename = typename std::enable_if<std::is_unsigned<T>::value>::type>
    S(T t) { }
};

int main() {
    S s1{42u};
    // S s2{42}; // this doesn't work
    // S s3{-1}; // this doesn't work
}

Вы можете вызвать ошибку компиляции, удалив нежелательную перегрузку.

Foo(int x) = delete;

Если вы хотите, чтобы вас предупреждали о каждом появлении такого кода, и вы используете GCC, используйте опцию -Wsign-conversion.

foo.cc: In function ‘int main()’:
foo.cc:8:19: warning: negative integer implicitly converted to unsigned type [-Wsign-conversion]
     Foo f = Foo(-1);     // how to get a compiler error here?
                   ^

Если вы хотите получить ошибку, используйте -Werror=sign-conversion.