2016-07-05 6 views
7

Ich habe eine header for optionally-lazy parameters erstellt (auch sichtbar in einer GitHub repository). (Dies ist not my first question based on the header.)Virtueller Destruktor ändert Verhalten von declType

Ich habe eine Basisklasse-Vorlage und zwei abgeleitete Klassenvorlagen. Die Basisklassenvorlage hat einen protected-Konstruktor mit einer static_assert. Dieser Konstruktor wird nur von einer bestimmten abgeleiteten Klasse aufgerufen. Innerhalb der static_assert benutze ich eine decltype.

Die wirklich bizarr ist, dass die Art der einen Namen innerhalb des decltype irgendwie durch die betroffen ist, ob es ein virtuelles destructor ist in meiner Basis-Klasse-Vorlage.

Hier ist meine MCVE:

#include <type_traits> 
#include <utility> 

template <typename T> 
class Base 
{ 
    protected: 
    template <typename U> 
    Base(U&& callable) 
    { 
     static_assert(
      std::is_same< 
       typename std::remove_reference<decltype(callable())>::type, T 
      >::value, 
      "Expression does not evaluate to correct type!"); 
    } 

    public: 
    virtual ~Base(void) =default; // Causes error 

    virtual operator T(void) =0; 
}; 

template <typename T, typename U> 
class Derived : public Base<T> 
{ 
    public: 
    Derived(U&& callable) : Base<T>{std::forward<U>(callable)} {} 

    operator T(void) override final 
    { 
     return {}; 
    } 
}; 

void TakesWrappedInt(Base<int>&&) {} 

template <typename U> 
auto MakeLazyInt(U&& callable) 
{ 
    return Derived< 
      typename std::remove_reference<decltype(callable())>::type, U>{ 
     std::forward<U>(callable)}; 
} 

int main() 
{ 
    TakesWrappedInt(MakeLazyInt([&](){return 3;})); 
} 

Beachten Sie, dass, wenn die destructor Kommentar gesetzt ist, dies ohne Fehler kompiliert wird.

Die Absicht ist für callable ein Ausdruck des Typs zu sein U, die, wenn sie mit dem () Operator genannt, T etwas vom Typ zurückgibt. Ohne den virtuellen Destruktor in Base scheint es, dass dies richtig ausgewertet wird; mit der virtuelle Destruktor, es scheint, dass callabele ist der Typ Base<T> (was, soweit ich sagen kann, keinen Sinn macht).

Hier G ++ 5.1 die Fehlermeldung:

recursive_lazy.cpp: In instantiation of ‘Base<T>::Base(U&&) [with U = Base<int>; T = int]’: 
recursive_lazy.cpp:25:7: required from ‘auto MakeLazyInt(U&&) [with U = main()::<lambda()>]’ 
recursive_lazy.cpp:48:47: required from here 
recursive_lazy.cpp:13:63: error: no match for call to ‘(Base<int>)()’ 
       typename std::remove_reference<decltype(callable())>::type, T 

Hier Clang ++ 3.7 die Fehlermeldung:

recursive_lazy.cpp:13:55: error: type 'Base<int>' does not provide a call operator 
       typename std::remove_reference<decltype(callable())>::type, T 
                 ^~~~~~~~ 
recursive_lazy.cpp:25:7: note: in instantiation of function template specialization 
     'Base<int>::Base<Base<int> >' requested here 
class Derived : public Base<T> 
    ^
1 error generated. 

Here is an online version.

EDIT:=delete -Ing die Kopie-Konstruktor auch löst diesen Fehler aus.

+2

ich Sie nicht über die seltsame virtuellen Destruktor Fehler sagen könnte, aber ich sehe, dass 'Abgeleitete (U && aufrufbar)' ein r-Wert annimmt Referenz und keine universelle Referenz. Ist das beabsichtigt? – SirGuy

+0

Könnten Sie dem Beispiel eine Ausgabe hinzufügen, die zeigt, was es tun soll? TakesWrappedInt scheint aufgrund des letzten Operators T(); in Ihrem Beispiel eine Null zu erhalten. –

+0

@GuyGreer Nein, es ist nicht beabsichtigt. Ist dies der Fall, weil U nach der Spezialisierung der Vorlage nicht mehr ein Schablonentyp ist? –

Antwort

10

Das Problem ist, dass, wenn Sie destructor, implizite Bewegung Konstruktor erklären nicht, weil

(N4594 12,8/9)

erklärt, wird nach der Definition einer Klasse X nicht ausdrücklich erklärt ein Umzug Konstruktor wird ein nicht-expliziten implizit deklariert werden als ausgefallen, wenn und nur wenn

...

  • X nicht über ein benutzer erklärt destructor

Base hat benutzer erklärt destructor (es spielt keine Rolle, dass es notleidenden ist).

Wenn MakeLazyInt versucht, konstruierte Derived Objekt zurückgibt, ruft es Derived Move Constructor.

Derived Implizit deklarierter Move-Konstruktor ruft Base nicht auf (weil das nicht existiert), sondern eher auf den Templates Base(U&&) Konstruktor.

Und hier ist das Problem, callable Parameter nicht aufrufbar Objekt, sondern Base Objekt enthalten, die wirklich nicht operator() enthalten.

das Problem einfach bewegen Konstruktor innerhalb Base erklären zu lösen:

template <typename T> 
class Base 
{ 
    protected: 
    template <typename U> 
    Base(U&& callable) 
    { 
     static_assert(
      std::is_same< 
       typename std::remove_reference<decltype(callable())>::type, T 
      >::value, 
      "Expression does not evaluate to correct type!"); 
    } 

    public: 
    virtual ~Base(void) =default; // When declared, no implicitly-declared move constructor is created 

    Base(Base&&){} //so we defined it ourselves 

    virtual operator T(void) =0; 
}; 
+1

... und das erklärt auch OP's Update über das Löschen der Kopie Erbauer ... – davidbak

+0

Ahhhhh. Ich kenne diese Regel, habe aber aufgrund der Fehlermeldung nicht gesehen, wie sie angewendet wurde. Vielen Dank. Weißt du, ob die garantierte Kopierfreiheit von C++ 17 dieses Problem verhindert hätte? –

+0

@KyleStrand Nun basierend auf der zweiten Regel * In einem Funktionsaufruf, wenn der Operand einer return-Anweisung ein prvalue ist und der Rückgabetyp der Funktion ist identisch mit dem Typ dieses prvalue. *, Ich denke, Compiler wird elide Konstruktor verschieben , aber ich bin mir nicht sicher, warum der Compiler es in diesem Fall nicht ausgeschaltet hat. – PcAF