2015-02-18 7 views
6

rozważyć poniższą kod:Pośrednie konwersji z int do 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 
} 

W pierwszym wierszu w main() liczba całkowita 0 przekształca się w std::shared_ptr<int> a połączenie f(0) udaje się bez problemu. Jednak użycie szablonu do wywołania funkcji powoduje, że rzeczy się różnią. Druga linia nie będzie już skompilować, błąd jest

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

moje pytania są:

  1. Dlaczego pierwsze połączenie uda, a druga nie? Czy jest tu coś, czego mi tu brakuje?
  2. Nie rozumiem też, jak konwersja z int do std::shared_ptr jest wykonywana w zaproszeniu f(0), jak to wygląda std::shared_ptr ma tylko jawnych konstruktorów.

PS: Wariant tym przykładzie pojawia się Scott Meyers' skutecznych, nowoczesnych C++ punktu 8, jako sposób ochrony tych połączeń z nullptr.

+0

@@ vsoftco należy wspomnieć, że to działa tylko dla 'f (0)', oprócz zera nic nie jest kompilacji samego błędu dla innych wartości nawet w pierwszym wywołaniu. –

+0

[This] (http://en.cppreference.com/w/cpp/language/nullptr) sugeruje wyjaśnienie - "Istnieją niejawne konwersje z nullptr na wartość wskaźnika zerowego dowolnego typu wskaźnika i dowolnego wskaźnika na typ pręta. Podobne konwersje istnieją dla dowolnej wartości typu std :: nullptr_t oraz dla makra NULL, stała wskaźnika zerowego. " Interesujące byłoby jednak, aby wyjaśnić to standardowymi odniesieniami i większą jasnością. – Pradhan

+1

@RupeshYadav. Gotowe. Rzeczywiście, jest to bardzo dziwne, wygląda na to, że wykonywana jest jakaś niejawna konwersja wskaźnika. – vsoftco

Odpowiedz

5

std::shared_ptr konstruktor które ma std :: nullptr_t, dosłownym 0 jest zerowy wskaźnik, który jest stały convertiable do std :: nullptr_t z projektu C++ standardowej sekcji 4.10[conv.ptr] (kopalni nacisk przyszłości)

null stały wskaźnik stanowi integralną wyrażenie stałe (5,19) prvalue od całkowitej typu, który ma wartość zero lub jest prvalue typu std :: nullptr_t.Stała wskaźnika zerowego może zostać przekonwertowana na typ wskaźnika ; wynikiem jest wartość pustego wskaźnika tego typu i jest ona możliwa do odróżnienia od każdej innej wartości wskaźnika obiektu lub typu wskaźnika. Taka konwersja nazywana jest konwersją wskaźnika pustego. Dwie puste wartości wskaźnika tego samego typu powinny być jednakowe. Konwersja stałej wskaźnika zerowego do wskaźnika na typ z kwalifikacją cv jest pojedynczą konwersją, a nie sekwencją konwersji wskaźnika, a następnie konwersją kwalifikacji (4.4). Stała wskaźnikowa o stałej całk. może zostać przekonwertowana na wartość o wartości typ std :: nullptr_t. [Uwaga: Wynikowa wartość prna nie jest zerową wartością wskaźnika. -end uwaga]

w drugim przypadku p jest wyprowadzona jako typ int który chociaż ma wartość zero nie jest już stały wskaźnik zerowy i tak nie pasuje do tej samej sprawy.

Jako T.C. wskazuje na treść została zmieniona z DR 903 co wymaga dosłownego liczbę całkowitą o wartości zerowej, w przeciwieństwie do integralnej stałej ekspresji który ocenia zeru:

null stały wskaźnik jest całkowitą dosłownym (2.14.2) z wartością zero lub wartością proksy typu std :: nullptr_t. Stała wskaźnika zerowego może zostać przekonwertowana na typ wskaźnika; wynikiem jest wartość zerowa tego wskaźnika i jest ona możliwa do odróżnienia od każdej innej wartości wskaźnika obiektu lub typu wskaźnika funkcji.

+0

Może chcesz użyć bardziej zaktualizowanego projektu (patrz odpowiedź @ Casey poniżej). IIRC "całkowite wyrażenie ciągłe (5.19) prvalue typu integer, który ocenia zero" zawiera takie klejnoty jak '(3 * 5 + 8 - 7)/4 - 4' i interweniuje dziwnie z rozdzielczością przeciążenia dla niezobowiązujących wywołań obejmujących nietypowe argumenty szablonu, więc zmienili je na literalne zero tylko w DR. –

+0

@ T.C. dziękuję za wskazanie tego, zaktualizowałem moją odpowiedź. –

+0

Wow, to jest jakiś poważny złamanie kodu, w którym głosowali. –

2

ciągu [conv.ptr]/1 (tu przytoczyć N4296)

NULL stałe jest liczbą całkowitą dosłownym (2.13.2) o wartości zero lub prvalue typu std::nullptr_t. ... Stała wskaźnika pustego typu całkowego może zostać przekonwertowana na wartość typu std::nullptr_t.

shared_ptr ma bez wyraźnej konstruktor przyjmuje std::nullptr_t ciągu [util.smartptr.shared.const]/1:

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

który konstruuje pustych, posiadający shared_ptr.

Po wywołaniu f(0) bezpośrednio 0 jest zerowy wskaźnik stały który jest niejawnie konwertowane do shared_ptr<int> powyższym konstruktora. Kiedy zamiast tego zadzwonisz pod numer call_f(f, 0), typ literalnego 0 zostanie wydedukowany na int i oczywiście nie można przekonwertować int na shared_ptr<int>.

1

Pierwsze wywołanie f (0) jest kompilowane jako f (nullptr), co jest dobre dla kompilatora (ale nie powinno to być moim zdaniem). Drugie wywołanie tworzy deklarację funkcji działającej na dowolnym int, co jest nielegalne.

Zabawne jest to, że nawet ten kod działa:

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

Tak, to ma sens po przeczytaniu powyższych odpowiedzi. Kod 'f (3-3)' jest w porządku, ponieważ '3-3' lub' 3 * 0' jest wartością ocenianą na 0. "Stała wskaźnika zerowego jest całkową stałą wyrażeniem (5.19) wartością typu całkowitego, która ocenia na zero lub wartość progową typu std :: nullptr_t. " Z drugiej strony 'int x = 0; f (x); 'nie będzie już działać, ponieważ' x' nie jest już wartością 0, ale l-wartością, a więc stałą wartości wskaźnika zerowego. – vsoftco