2016-03-22 12 views
30

Angenommen, Sie haben dies:Wie kann die implizite Konvertierung von int in unsigned int verhindert werden?

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

Ist es möglich, die implizite Konvertierung zu verhindern?

Der einzige Weg, ich denken konnte, ist explicilty einen Konstruktor zur Verfügung stellen, die ein int nimmt und eine Art von Laufzeitfehler, wenn die int negativ ist, aber es wäre schöner, wenn ich einen Compiler-Fehler für diese bekommen kann.

Ich bin mir fast sicher, dass es ein Duplikat gibt, aber die nächste, die ich finden konnte, ist this question, die eher fragt, warum die implizite Konvertierung erlaubt ist.

Ich interessiere mich für beide, C++ 11 und pre C++ 11 Lösungen, vorzugsweise eine, die in beiden funktionieren würde.

Antwort

28

Gleichmäßige Initialisierung verhindert Verengungen.

Es folgt ein (nicht funktioniert, wie gewünscht) Beispiel:

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

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

einfach zu verwenden die einheitliche Initialisierung gewöhnen (Foo{-1} statt Foo(-1)) wo immer möglich.

EDIT

Als Alternative, wie von der OP in den Kommentaren aufgefordert, eine Lösung, die auch mit C++ 98 ist zu erklären, wie private die Konstrukteure immer ein int (long int, und so weiter arbeitet).
Keine Notwendigkeit, sie tatsächlich zu definieren.
Bitte beachten Sie, dass = delete auch eine gute Lösung wäre, wie in einer anderen Antwort vorgeschlagen, aber das ist auch seit C++ 11.

EDIT 2

Ich möchte eine weitere Lösung hinzuzufügen, Ereignis, obwohl es seit C++ 11 gültig ist.
Die Idee basiert auf dem Vorschlag von Voo (siehe die Kommentare von Brians Antwort für weitere Details) und verwendet SFINAE für Konstruktorargumente.
Es folgt eine minimale, Arbeitsbeispiel:

#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 
} 
+2

nur möglich in C++ 11, oder? – user463035818

+1

Ja, hast du gesagt, dass du C++ 11 nicht benutzen kannst? – skypjack

+0

nein ich habe nicht, aber wenn es eine Lösung gibt, die gut in pre C++ 11 und in post C++ 11 funktioniert, bevorzuge ich diese. Leider kann ich im größten Teil meines Codes nicht C++ 11 – user463035818

26

Sie können einen Kompilierungsfehler erzwingen, indem Sie die unerwünschte Überladung löschen.

Foo(int x) = delete; 
+6

'Foo f (42)' wäre verboten (auch wenn '42' positiv ist) (während' Foo f (42u) 'funktioniert). – Jarod42

+1

@ Jarod42, wahr.Aber unvergänglich :) – SergeyA

+0

@Sergey Für Kompilierzeit-Konstanten könnten wir es mit einigen SFINAE-Überladungs- und Hilfsmethoden vermeiden, aber ich denke nicht, dass man die beiden Methoden kombinieren könnte, um Ints generell zu verbieten, aber int-Konstanten zuzulassen. – Voo

8

Wenn Sie auf jedes Auftreten eines solchen Codes gewarnt werden, und Sie verwenden GCC, verwenden Sie die Option -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? 
       ^

Wenn Sie einen Fehler wünschen, verwenden Sie -Werror=sign-conversion.