2015-02-18 10 views
6

den Code Betrachten unter:implizite Konvertierung von Int Shared_ptr

#include <iostream> 
#include <memory> 

void f(std::shared_ptr<int> sp) {} 

template <typename FuncType, typename PtrType> 
auto call_f(FuncType f, PtrType p) -> decltype(f(p)) 
{ 
    return f(p); 
} 

int main() 
{ 
    f(0); // doesn't work for any other int != 0, thanks @Rupesh 
    // call_f(f, 0); // error, cannot convert int to shared_ptr 
} 

In der ersten Zeile in main(), die ganze Zahl 0 wird einem std::shared_ptr<int> umgewandelt und der Anruf f(0) gelingt es ohne Probleme. Wenn Sie jedoch eine Vorlage verwenden, um die Funktion aufzurufen, unterscheiden Sie sich davon. Zweite Zeile nicht mehr kompilieren, wobei der Fehler

error: could not convert 'p' from 'int' to 'std::shared_ptr<int>' 

Meine Fragen sind:

  1. Warum der erste Aufruf erfolgreich sein und die zweite nicht? Gibt es etwas, was mir hier fehlt?
  2. Ich verstehe auch nicht, wie die Umwandlung von int zu std::shared_ptr in dem Aufruf f(0) durchgeführt wird, wie es aussieht std::shared_ptr hat nur explizite Konstruktoren.

PS: Eine Variante dieses Beispiels wird in Scott Meyers' Effective Moderne C++ Punkt 8, als ein Weg des Schützens solche Anrufe mit nullptr.

+0

@@ vsoftco sollten Sie erwähnen, dass es auch für andere nur für 'f (0)', abgesehen von Null nichts kompiliert, gleiche Fehler arbeitet Werte sogar beim ersten Anruf. –

+0

[This] (http://en.cppreference.com/w/cpp/language/nullptr) deutet auf eine Erklärung hin - "Es gibt implizite Konvertierungen von nullptr in den Nullzeigerwert eines beliebigen Zeigertyps und jeden Zeiger auf den Elementtyp. Ähnliche Konvertierungen gibt es für jeden Wert vom Typ std :: nullptr_t sowie für den Makro NULL, die Null-Zeiger-Konstante. " Aber es wäre interessant zu sehen, dass es mit Standardreferenzen und mehr Klarheit erklärt wird. – Pradhan

+1

@RupeshYadav. erledigt. In der Tat ist es sehr seltsam, sieht aus wie eine Art impliziter Zeiger Umwandlung durchgeführt wird. – vsoftco

Antwort

5

std::shared_ptr hat einen Konstruktor, std :: nullptr_t, literal 0 ist ein Null-Zeiger-Konstante, die std :: ist Convertiable nimmt nullptr_t aus dem Entwurf des C++ Standard-Teil 4.10[conv.ptr] (Minen Schwerpunkt geht nach vorn):

ein Nullzeiger Konstante ist ein integraler konstanter Ausdruck (5.19) prvalue ganzzahliger Typ, der auf Null oder eine prvalue vom Typ auswertet std :: nullptr_t.Eine Nullzeigerkonstante kann in einen Zeigertyp umgewandelt werden; Das Ergebnis ist der Nullzeigerwert dieses Typs und ist , der von jedem anderen Wert des Objektzeigers oder der Funktion des Zeigertyps unterscheidbar ist. Eine solche Konvertierung wird als Nullzeiger-Konvertierung bezeichnet. Zwei Nullzeigerwerte des gleichen Typs müssen gleich sein. Die Konvertierung einer Null-Zeiger-Konstante in einen Zeiger auf cv-qualifizierte Typ ist eine einzelne Konvertierung, und nicht die Sequenz eines Zeigers Umwandlung gefolgt von einer Qualifizierung Konvertierung (4.4). Eine Null- Zeigerkonstante des Integraltyps kann in einen Pr-Wert von Typ std :: nullptr_t konvertiert werden. [Hinweis: Der resultierende Prvalue ist kein Null-Wert Zeiger. -Ende note]

im zweiten Fall p wird als Typ abgeleitete int, die zwar den Wert Null ist nicht mehr ein Null-Konstante Zeiger und so nicht den gleichen Fall paßt.

als T.C. weist darauf hin, wurde die Formulierung mit DR 903 geändert, die eine Ganzzahlliteral mit dem Wert Null im Gegensatz zu einem integralen konstanten Ausdruck erfordert, die auf Null auswertet:

Ein Nullzeiger Konstante ist ein Ganzzahlliteral (2.14.2) mit Wert Null oder ein Prvalue des Typs std :: nullptr_t. Eine Nullzeigerkonstante kann in einen Zeigertyp umgewandelt werden; Das Ergebnis ist der Nullzeiger Wert dieses Typs und unterscheidet sich von jedem anderen Wert Objektzeiger oder Funktionszeigertyp.

+0

Möglicherweise möchten Sie einen aktuelleren Entwurf verwenden (siehe @ Caseys Antwort unten). IIRC "Integralkonstantenausdruck (5.19) prvalue des Integer-Typs, der zu Null auswertet" umfasst solche Edelsteine ​​wie '(3 * 5 + 8 - 7)/4 - 4' und interagiert seltsam mit der Überladungsauflösung für nicht-abhängige Aufrufe, die Nicht-Typ beinhalten Template-Argumente, so dass sie nur in einem DR in Literal-Null geändert wurden. –

+0

@ T.C. Danke, dass du darauf hingewiesen hast, habe meine Antwort aktualisiert. –

+0

Wow, das ist ein großer Code-Bruch, den sie gewählt haben. –

2

Per [conv.ptr]/1 (unter Angabe N4296 hier):

A Nullzeiger Konstante eine ganze Zahl literal (2.13.2) mit dem Wert Null oder einem prvalue vom Typ std::nullptr_t. ... Eine Null-Zeiger-Konstante vom Integraltyp kann in einen Pr-Wert vom Typ std::nullptr_t konvertiert werden.

shared_ptr hat einen nicht-Konstruktor, die expliziten std::nullptr_t pro [util.smartptr.shared.const]/1 übernimmt:

constexpr shared_ptr(nullptr_t) noexcept : shared_ptr() { } 

, die ein leere Konstrukte, nicht-besitzende shared_ptr.

Wenn Sie f(0) direkt anrufen, 0 ist ein Null-Zeiger-Konstante, die shared_ptr<int> durch die oben Konstruktor implizit umgewandelt wird. Wenn Sie stattdessen call_f(f, 0) aufrufen, wird der Typ des Literals 0 auf int und natürlich ein int kann nicht in eine shared_ptr<int> konvertiert werden.

1

Der erste Aufruf von f (0) ist kompiliert als f (nullptr), was für den Compiler in Ordnung ist (aber meiner Meinung nach nicht). Der zweite Aufruf erstellt eine Deklaration für eine Funktion, die an jedem int arbeiten soll, was illegal ist.

Das lustige daran ist, dass auch dieser Code funktioniert:

f(3-3); 
f(3*0); 
+0

Ja, es macht Sinn nach dem Lesen der Antworten oben. Der Code 'f (3-3)' ist in Ordnung, da '3-3' oder' 3 * 0' ein Prvalue ist, der zu 0 ausgewertet wird. "Eine Nullzeigerkonstante ist eine Integralkonstantenausdruck (5.19) Prvalue vom Integer-Typ ergibt null oder einen prvalue vom Typ std :: nullptr_t. " Auf der anderen Seite ist int x = 0; f (x); 'wird nicht mehr funktionieren, da' x' kein 0 prvalue mehr ist, sondern ein lvalue, also keine Nullzeigerkonstante mehr. – vsoftco