2017-01-25 3 views
4

Ich finde oft, dass ich Konvertierungen (und allgemein die Usual arithmetic conversions) für bestimmte Konstruktoren oder Funktionen verhindern möchte. Ich neige dazu, schreiben:Verwendung von (Templated) gelöschte Funktionsüberladung, um übliche arithmetische Umwandlungen zu vermeiden

#include <iostream> 

void foo(double f){ 
    std::cout << "foo double" << f <<std::endl; 
} 
void foo(float) = delete; 
// or template<typename T> void foo(T&& f) = delete; 

void bar(unsigned int f){ 
    std::cout << "bar uint " << f <<std::endl; 
} 
void bar(signed int) = delete; 
// or template<typename T> void bar(T&& f) = delete; 

Dies macht den Job ...

int main() { 
    auto i=2; 
    auto d=2.0; 
    auto f=2.0f; 
    foo(i); // prevented 
    foo(d); // OK 
    foo(f); // prevented 

    auto uil = 3ull; 
    auto ul = 3ul; 
    auto u = 3u; 
    bar(i); // prevented 
    bar(d); // prevented 
    bar(f); // prevented 
    bar(uil); // prevented 
    bar(ul); // prevented 
    bar(u); // OK 
} 

Nun ist es nur eine Frage des Geschmacks in diesen Fällen, wenn ich eine gelöschte Vorlage oder eine gelöschte Nicht-Vorlage verwenden Funktion, oder gibt es Fälle, in denen es darauf ankommt? Ich finde die gelöschte Vorlage expliziter, indem sie verhindert alle T, aber auf der anderen Seite, wenn dieses Muster mit Konstruktoren verwenden; Weiterleitungskonstruktoren have their issues. Im Fall der Vorlagenversion, wäre es besser, die gelöschte Vorlage stattdessen const T& zu machen?

+2

oder einfach 'T' (' Vorlage void foo (T) = löschen; '). – Jarod42

+0

@ Jarod42. Richtig ... –

+1

Ein obskurer Fall, in dem sich die Dinge anders verhalten, ist etwas wie 'foo ({f});', was durch die Template-Version nicht verhindert wird, weil die Initialisierungsliste den Parameter zu einem nicht-abgeleiteten Kontext macht, so dass der Abzug fehlschlägt für die Vorlage, wobei nur die Nicht-Vorlage-Überladung verbleibt. (Ich kann den Link in der Frage nicht öffnen, ich weiß nicht, ob das dort erwähnt wurde.) – bogdan

Antwort

3

Erstens, ich denke, es ist erwähnenswert, dass die Nicht-Template-Versionen die meisten Fälle verhindern, da sie eine Mehrdeutigkeit zwischen den beiden Überladungen verursachen, während die Template-Vorlagen eine bessere Übereinstimmung als die Nicht-Template-Überladung bieten. Aus diesem Grund werden die Fehlermeldungen, die von den Vorlagenversionen erzeugt werden, tendenziell klarer, im Sinne von "Sie haben versucht, diese gelöschte Funktion aufzurufen", im Gegensatz zu "Ich kann mich nicht zwischen diesen beiden entscheiden willst du eigentlich? ". Aus dieser Sicht sieht die Template-Version besser aus.

Es gibt jedoch Fälle, in denen sich die Dinge anders verhalten.

Ein obskurer Fall ist etwas wie foo({f});, was durch die Template-Version nicht verhindert wird, weil die Initialisiererliste den Parameter zu einem nicht-abgeleiteten Kontext macht, so dass der Abzug für die Vorlage fehlschlägt und nur die Nicht-Template-Überladung verbleibt.

Aus einem ähnlichen Grund wird die Template-Version foo({i}); blockieren, aber blockiert nicht foo({3}); (3 ist eine Konstante, so dass die Umstellung auf double ist keine einschränkende Konvertierung, weil 3 in einem double passt und erzeugt den gleichen Wert, wenn zurück konvertiert). Die Nicht-Template-Version blockiert beide, da sie beide nicht eindeutig sind.

Ein anderer Fall:

enum E : unsigned { }; 

int main() 
{ 
    E e{}; 
    bar(e); 
} 

Die Template-Version verhindert dies, indem Sie die beste Überlastung bereitstellt. Das Nicht-Template ist nicht, weil E bis unsigned int eine Promotion ist, die besser ist als E bis int, was eine Konvertierung ist.

ähnliche Probleme würden auf Plattformen erscheinen, wo zum Beispiel short die gleiche Größe wie int ist, und auch für Konvertierungen von char16_t, char32_t oder wchar_t, abhängig von ihren plattformspezifischen Darstellungen.

Obwohl wahrscheinlich weniger interessant für den Kontext der Frage, erscheint ein weiterer Unterschied für:

struct A 
{ 
    operator double() { return 7.0; } 
}; 

int main() 
{ 
    A a{}; 
    foo(a); 
} 

Die Template-Version verhindert dies, wiederum durch die beste Überlastung bereitstellt, während die nicht-Vorlage tut man nicht (Anrufe foo(double)).

+0

Ich würde argumentieren, dass nicht 'foo fangen ({f}) 'ist in vielen Anwendungsfällen in Ordnung, da die Klammern bereits eine Verengung verhindern. –

+0

@ T.C. Und ich stimme dem zu, aber das OP möchte 'foo (f);' blockieren, so dass die versperrte Version nicht blockiert wird, fühlt sich an wie eine Inkonsistenz. Ich versuche hauptsächlich die Fälle zu finden, in denen die beiden Methoden zu unterschiedlichen Ergebnissen führen. Ich vermeide die Entscheidung, was eigentlich sinnvoll ist, zu blockieren oder zuzulassen - zu schwierig für mich. – bogdan

+1

@ T.C., Ich verstehe nicht. f = 1.5f mit foo ({f}) konvertiert 1.5f in 1U. http://ideone.com/HYlg5D. –

Verwandte Themen