2016-07-05 106 views
7

Ho creato un header for optionally-lazy parameters (visibile anche in un GitHub repository). (Questo è not my first question based on the header.)Il distruttore virtuale modifica il comportamento di decltype

Ho un modello di classe base e due modelli di classe derivata. Il modello di classe base ha un costruttore protected con un static_assert. Questo costruttore è chiamato solo da una particolare classe derivata. All'interno del static_assert sto usando un decltype.

La cosa veramente strana è che il tipo di un nome all'interno del decltype è in qualche modo influenzata dalla presenza o meno di un distruttore virtuale nel mio modello di base di classe.

Ecco la mia 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;})); 
} 

Nota che se il distruttore è commentata, questo viene compilato senza errori.

L'intento è per callable di essere un'espressione di tipo U che, quando viene chiamato con l'operatore (), restituisce qualcosa di tipo T. Senza il distruttore virtuale in Base, sembra che questo sia stato valutato correttamente; con il distruttore virtuale, sembra che il tipo callabele sia Base<T> (che, per quanto posso dire, non ha senso).

Ecco G ++ messaggio di errore 5.1 del:

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 

Ecco Clang ++ messaggio di errore 3.7 del:

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 il costruttore di copia anche attiva questo errore.

+2

non ho potuto dirvi sull'errore distruttore virtuale strano, ma vedo che 'Derivato (U && callable)' accetta un valore di R riferimento e non un riferimento universale. È destinato? – SirGuy

+0

Potresti aggiungere qualche output all'esempio che mostra cosa dovrebbe fare. TakesWrappedInt sembra ottenere uno zero con il tuo esempio, a causa dell'operatore finale T() ;. –

+0

@GuyGreer No, non è previsto. È perché, una volta specializzato il modello, U non è più un tipo di modello? –

risposta

10

Il problema è che quando si dichiara distruttore, costruttore implicita mossa non sarà dichiarata, perché

(N4594 12,8/9)

Se la definizione di una classe X non dichiara esplicitamente un costruttore mossa, una non esplicita sarà implicitamente dichiarato come default se e solo se

...

  • X non ha un distruttore dall'utente dichiarato

Base ha distruttore user-dichiarata (non importa che sia in default).

Quando MakeLazyInt tenta di restituire l'oggetto Derived creato, chiama il costruttore di spostamento Derived.

Derived Il costruttore di movimento implicitamente dichiarato non chiama Base costruttore di movimento (perché non esiste), ma piuttosto il costruttore di modelli Base(U&&).

Ed ecco il problema, callable parametro non contiene richiamabile oggetto ma Base oggetto, che in realtà non contiene operator().

Per risolvere il problema è sufficiente dichiarare costruttore mossa all'interno Base:

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

... e questo spiega anche l'aggiornamento di OP sull'eliminazione della copia costruttore ... – davidbak

+0

Ahhhhh. Conosco questa regola ma non vedo come si applica, in base al messaggio di errore. Grazie. Sai se la copia-ellisione garantita da C++ 17 avrebbe evitato questo problema? –

+0

@KyleStrand Ben basato sulla seconda regola * In una chiamata di funzione, se l'operando di un'istruzione return è un valore di prvalore e il tipo restituito della funzione è uguale al tipo di valore di prvalue. *, Penso che il compilatore eliderà il costruttore di mosse , ma non sono sicuro del motivo per cui il compilatore non l'ha eluito in questo caso. – PcAF