Как предотвратить неявное преобразование из 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, желательно то, которое будет работать в обоих.


person 463035818_is_not_a_number    schedule 22.03.2016    source источник


Ответы (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)) везде, где это возможно.

ИЗМЕНИТЬ

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

ИЗМЕНИТЬ 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
}
person skypjack    schedule 22.03.2016
comment
возможно только в C ++ 11, не так ли? - person 463035818_is_not_a_number; 22.03.2016
comment
Да, вы сказали, что не можете использовать C ++ 11? - person skypjack; 22.03.2016
comment
нет, я этого не делал, но если существует решение, которое хорошо работает в pre c ++ 11 и post c ++ 11, я предпочитаю это. К сожалению, в большей части моего кода я не могу использовать c ++ 11 - person 463035818_is_not_a_number; 22.03.2016
comment
и кроме С ++ 11 или нет, это на самом деле не решает мою проблему, потому что Foo f= Foo(-1); все еще разрешено, если я не заставлю всех, использующих мой код, использовать единую инициализацию - person 463035818_is_not_a_number; 22.03.2016
comment
Обновлено. К сожалению, даже = delete начиная с C ++ 11. - person skypjack; 22.03.2016
comment
@ tobi303 Обычно я ожидаю, что разработчик, который не использует единую инициализацию, также знает, в чем заключаются недостатки. Например, сужение. Вы не можете заставить других писать хороший код. :-) - person skypjack; 22.03.2016
comment
на самом деле я не понимал, что =delete существует с C ++ 11, извините за путаницу и спасибо за подсказку с частным конструктором. - person 463035818_is_not_a_number; 22.03.2016
comment
Обновлено с примечанием о =delete, чтобы помочь будущим читателям. Добро пожаловать. - person skypjack; 22.03.2016
comment
@ tobi303 но если существует решение, которое хорошо работает в pre c ++ 11 и post c ++ 11, я предпочитаю это как насчет private: Foo(int x);? - person zdf; 22.03.2016
comment
Также помните о ключевом слове explicit - en.cppreference.com/w / cpp / keyword / explicit - person Jesper Juhl; 22.03.2016
comment
@JesperJuhl ideone не жаловался на явный конструктор, вызываемый через Foo(-1). Я должен признать, что совершенно не знаком с большинством вещей, связанных с C ++ 11. - person 463035818_is_not_a_number; 22.03.2016
comment
@ tobi303 Я знаю. Я просто подумал, что это актуально и связано. - person Jesper Juhl; 22.03.2016
comment
@ tobi303 Ради любопытства я добавил еще одно предложение. Я надеюсь тебе это понравится. - person skypjack; 23.03.2016
comment
Я отредактировал свой вопрос, пытаясь прояснить эту проблему c ++ 11 или pre c ++ 11 (и удалил тег c ++ 03). Ваш ответ по-прежнему подходит, даже лучше, чем раньше, и я усвоил урок. Я должен быть более точным об этом в будущем;) - person 463035818_is_not_a_number; 24.03.2016
comment
Нет проблем, это вопрос обновления ответа, чтобы добавить больше деталей. ;-) - person skypjack; 25.03.2016

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

Foo(int x) = delete;
person Brian Bi    schedule 22.03.2016
comment
Foo f(42) будет запрещено (даже если 42 положительно) (тогда как Foo f(42u) работает). - person Jarod42; 22.03.2016
comment
@ Jarod42, правда. Но неизбежно :) - person SergeyA; 22.03.2016
comment
@Sergey Для констант времени компиляции мы могли бы избежать этого с помощью некоторых методов перегрузки SFINAE и вспомогательных методов, я думаю, но я не думаю, что можно было бы объединить два метода, чтобы запретить целые числа в целом, но разрешить константы int. - person Voo; 23.03.2016

Если вы хотите получать предупреждения о каждом появлении такого кода, и вы используете 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.

person Nate Eldredge    schedule 23.03.2016