13

Durante la lettura this question, ho trovato uno strano punto:C++ utilizzando dichiarazione con la typename a ereditare-costruttori

template <typename T> 
class Subclass : public Baseclass<T> 
{ 
public: 
    using typename Baseclass<T>::Baseclass; 
    // ^^^^^^^^ 
}; 

Dal typename, Baseclass<T>::Baseclass dovrebbe essere injected class name, non un costruttore. Per quanto ne so, è lo stesso caso come questo:

template <typename T> 
class Base 
{ 
public: 
    typedef short some_type; 
}; 

template <typename T> 
class Sub : public Base<T> 
{ 
public: 
    using typename Base<T>::some_type; 
}; 

Per essere sicuri, ho scritto un codice di prova.

#include <iostream> 

template <typename T> 
class Base 
{ 
public: 
    Base() { std::cout << "A::A()\n"; } 
    Base(int) { std::cout << "A::A(int)\n"; } 
    Base(const char *) { std::cout << "A::A(const char *)\n"; } 
}; 

template <typename T> 
class Sub : public Base<T> 
{ 
    using typename Base<T>::Base; 
}; 

int main() 
{ 
    Sub<char> s1; 
    Sub<char> s2(3); 
    Sub<char> s3("asdf"); 
} 

Tuttavia, gcc viene eseguito su 4.8.3.

$ g++ -std=c++1y -Wall -Wextra -Werror -pedantic test.cpp -o test && ./test 
A::A() 
A::A(int) 
A::A(const char *) 

Funziona anche senza typename.

$ cat test.cpp 
... 
    using Base<T>::Base; 
... 

$ g++ -std=c++1y -Wall -Wextra -Werror -pedantic test.cpp -o test && ./test 
A::A() 
A::A(int) 
A::A(const char *) 

Perché ho ottenuto questi risultati? Cosa mi sono perso?

+3

clang ++ respinge il ' typename'. – dyp

+5

Mi sono preoccupato troppo della mia sanità mentale per rispondere a questa domanda sull'altra domanda ... Lo standard dice in [namespace.udecl]/1 "Se una * using-declaration * nomina un costruttore, dichiara implicitamente un insieme di costruttori nel classe in cui appare * using-declaration *, altrimenti il ​​nome specificato in * using-declaration * è un sinonimo di per il nome di qualche entità dichiarata altrove. " Ma in [class.ctor]/1 "I costruttori non hanno nomi". – dyp

+1

Nota che c'è [spazio dei nomi.udecl]/20 "Se una * using-declaration * utilizza la parola chiave' typename' e specifica un nome dipendente, il nome introdotto dalla * using-declaration * è considerato come * typedef-name *. " – dyp

risposta

1

lo standard è abbastanza chiaro su questo ([namespace.udecl]/1)

dichiarazione using:

utilizzando typename_opt nested-nome-identificatore qualificato-id;

Il typename parola chiave è dunque un optional parte di una dichiarazione secondo che può apparire anche per l'utilizzo di dichiarazioni di non-tipi. Il seguente codice dovrebbe quindi essere conforme norma:

template < typename T > class Base { 
    protected: 
    typedef T Ttype; 
    Ttype member; 

    public: 
    Base() { 
     std::cout << "A::A()\n"; 
    } 
    Base(int) { 
     std::cout << "A::A(int)\n"; 
    } 
    Base(const char *) { 
     std::cout << "A::A(const char *)\n"; 
    } 

    protected: 
    void memfunc(void) { 
     std::cout << "A::memfunc(void)\n"; 
    } 
}; 

template< typename T > 
struct SubNoTypename : protected Base<T> { 
    using Base<T>::Base; 
    using Base<T>::member; 
    using Base<T>::memfunc; 
    using Base<T>::Ttype; // n.b. no error in clang++ 
}; 

template< typename T > 
struct SubTypename : protected Base<T> { 
    using typename Base<T>::Base; // error in clang++ 
    using typename Base<T>::member; // error in clang++ 
    using typename Base<T>::memfunc; // error in clang++ 
    using typename Base<T>::Ttype; 
}; 

Sia SubNoTypename e SubTypename sono riconosciuti come conforme standard con gcc. D'altra parte clang ++ si lamenta in SubTypename sulle parole chiave mal posizionate typename. Tuttavia, questo non è nemmeno coerente, perché dovrebbe quindi lamentarsi di un typename mancante in using Base<T>::Ttype;. Questo è chiaramente un bug clang.


Modifica Il typename parola chiave è consentita anche se la classe base è nessuna classe template, un luogo dove normalmente si sarebbe mai aspettare questa parola chiave sia valido:

class BaseNoTemplate { 
    protected: 
    typedef T Ttype; 
    Ttype member; 

    public: 
    BaseNoTemplate() { 
     std::cout << "A::A()\n"; 
    } 
    BaseNoTemplate(const char *) { 
     std::cout << "A::A(const char *)\n"; 
    } 

    void memfunc(void) { 
     std::cout << "A::memfunc(void)\n"; 
    } 
}; 

struct SubNoTemplateNoTypename : protected BaseNoTemplate { 
    using BaseNoTemplate::BaseNoTemplate; 
    using BaseNoTemplate::member; 
    using BaseNoTemplate::memfunc; 
    using BaseNoTemplate::Ttype; 
}; 

struct SubNoTemplateTypename : protected BaseNoTemplate { 
    using typename BaseNoTemplate::BaseNoTemplate; // error in clang++ 
    using typename BaseNoTemplate::member; // error in clang++ 
    using typename BaseNoTemplate::memfunc; // error in clang++ 
    using typename BaseNoTemplate::Ttype; // n.b. no error in clang++ 
}; 
+0

Grazie per la tua risposta alla mia vecchia domanda senza risposta! Il problema è stato risolto, ma non posso fare a meno di chiedermi * perché * lo standard permetta * typename_opt * all'interno di 'using' declaration - sembra non essere necessario, penso :) – ikh

+1

Scusa, ma questo non ha alcun senso. Il fatto che la grammatica permetta la parola chiave 'typename' per non-types è semplicemente perché la restrizione che' typename' è usata per i tipi è una restrizione semantica piuttosto che una sintattica. (Ma se si affrontano anche le restrizioni semantiche, se ce ne sono, questa potrebbe essere una buona risposta.) – hvd

+0

@hvd Ho aggiunto alcuni esempi che mostrano l'uso di typename per classi base non-template. Imho questa è una domanda sintattica, non semantica. – user1978011

0

Siamo spiacenti, perché vuoi using, perché non solo typedef?

template <typename T> 
class Sub : public Base<T> 
{ 
    typedef Base<T> Base; 
}; 
+2

Si dovrebbe cercare su "Costruttori ereditari di C++" :) – ikh

Problemi correlati