How to prevent implicit conversion from int to unsigned int?
Asked Answered
I

3

30

Suppose you have this:

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;
}

Is it possible to prevent the implicit conversion?

The only way I could think of is to explicilty provide a constructor that takes an int and generates some kind of runtime error if the int is negative, but it would be nicer if I could get a compiler error for this.

I am almost sure, that there is a duplicate, but the closest I could find is this question which rather asks why the implicit conversion is allowed.

I am interested in both, C++11 and pre C++11 solutions, preferably one that would work in both.

Iyar answered 22/3, 2016 at 18:10 Comment(0)
H
30

Uniform initialization prevents narrowing.

It follows a (not working, as requested) example:

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

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

Simply get used to using the uniform initialization (Foo{-1} instead of Foo(-1)) wherever possible.

EDIT

As an alternative, as requested by the OP in the comments, a solution that works also with C++98 is to declare as private the constructors getting an int (long int, and so on).
No need actually to define them.
Please, note that = delete would be also a good solution, as suggested in another answer, but that one too is since C++11.

EDIT 2

I'd like to add one more solution, event though it's valid since C++11.
The idea is based on the suggestion of Voo (see the comments of Brian's response for further details), and uses SFINAE on constructor's arguments.
It follows a minimal, working example:

#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
}
Helmuth answered 22/3, 2016 at 18:23 Comment(15)
only possible in c++11, isnt it?Iyar
Yeah, did you say that you cannot use C++11?Helmuth
no I didnt, but if there exists a solution that works nicely in pre c++11 and in post c++11 I prefer that one. Unfortunately in the biggest fraction of my code I cannot use c++11Iyar
and apart from c++11 or not, it doesnt really solve my problem, because Foo f= Foo(-1); is still allowed unless I force everybody using my code to use uniform initializationIyar
Updated. Unfortunately, even = delete is since C++11.Helmuth
@tobi303 I usually expect that a developer that doesn't use uniform initialization also knows what are the drawbacks. As an example, narrowing. You cannot force the others to write good code. :-)Helmuth
actually I didnt realize that =delete is since c++11, sorry for the confusion and thanks for the tip with the private constructor.Iyar
Updated with a note about =delete, so as to help future readers. You are welcome.Helmuth
@tobi303 " but if there exists a solution that works nicely in pre c++11 and in post c++11 I prefer that one" How about private: Foo(int x);?Fake
On a similar note, also be aware of the explicit keyword - en.cppreference.com/w/cpp/keyword/explicitPolenta
@JesperJuhl ideone didnt complain about explicit constructor called via Foo(-1). I have to admit I am completely unfamiliar with most c++11 stuffIyar
@tobi303 I know. I just thought it was relevant and related.Polenta
@tobi303 I've added one more suggestion, for the sake of curiosity. I hope you like it.Helmuth
I edited my question trying to clarify this c++11 or pre c++11 issue (and removed the c++03 tag). Your answer still fits, actually better than before and I learned the lesson. I should be more explicit about this in the future ;)Iyar
No problem, it's the matter of updating an answer to add more details. ;-)Helmuth
S
27

You can force a compile error by deleting the undesired overload.

Foo(int x) = delete;
Sjoberg answered 22/3, 2016 at 18:11 Comment(3)
Foo f(42) would be prohibited (even if 42 is positive) (whereas Foo f(42u) works).Fluoride
@Jarod42, true. But unvoidable :)Representational
@Sergey For compile time constants we could avoid it with some SFINAE overloading and helper methods I guess, but I don't think one could combine the two methods as to disallow ints in general but allow int constants.Oppugnant
K
8

If you want to be warned on every occurrence of such code, and you're using GCC, use the -Wsign-conversion option.

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?
                   ^

If you want an error, use -Werror=sign-conversion.

Karyotype answered 23/3, 2016 at 3:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.