Posso implementare un tipo di membro autonomo `self` in C ++?

Il C ++ manca dell’equivalente della parola chiave self di PHP , che valuta il tipo della class che racchiude.

È abbastanza facile fingere su una base per class:

 struct Foo { typedef Foo self; }; 

ma ho dovuto scrivere di nuovo Foo . Forse un giorno mi sbaglierò e causerò un bug silenzioso.

Posso usare una combinazione di decltype e amici per rendere questo lavoro “autonomamente”? Ho già provato quanto segue, ma this non è valido in quel luogo:

 struct Foo { typedef decltype(*this) self; }; // main.cpp:3:22: error: invalid use of 'this' at top level // typedef decltype(*this) self; 

(Non mi preoccuperò dell’equivalente dello static , che fa lo stesso ma con un’associazione tardiva.)

Ecco come puoi farlo senza ripetere il tipo di Foo:

 template  class Self; template  class Self : public Ts... { protected: typedef X self; }; #define WITH_SELF(X) X : public Self #define WITH_SELF_DERIVED(X,...) X : public Self class WITH_SELF(Foo) { void test() { self foo; } }; 

Se vuoi ottenere da Foo , dovresti utilizzare la macro WITH_SELF_DERIVED nel seguente modo:

 class WITH_SELF_DERIVED(Bar,Foo) { /* ... */ }; 

Puoi persino fare più ereditarietà con tutte le classi base che vuoi (grazie a modelli variadic e macro variadic):

 class WITH_SELF(Foo2) { /* ... */ }; class WITH_SELF_DERIVED(Bar2,Foo,Foo2) { /* ... */ }; 

Ho verificato che funzioni su gcc 4.8 e clang 3.4.

Puoi usare una macro invece di una normale dichiarazione di class, che lo farà per te.

 #define CLASS_WITH_SELF(X) class X { typedef X self; 

E poi usare come

 CLASS_WITH_SELF(Foo) }; 

#define END_CLASS }; probabilmente aiuterebbe la leggibilità.


Potresti anche prendere il Self di Paranaix e usarlo (inizia a diventare veramente hackerato)

 #define WITH_SELF(X) X : public Self class WITH_SELF(Foo) { }; 

Una soluzione ansible (dato che devi scrivere il tipo una volta sola):

 template struct Self { protected: typedef T self; }; struct Foo : public Self { void test() { self obj; } }; 

Per una versione più sicura possiamo assicurare che T deriva in realtà da Self :

 Self() { static_assert(std::is_base_of, T>::value, "Wrong type passed to Self"); } 

Si noti che un static_assert all’interno di una funzione membro è probabilmente l’unico modo per controllare, poiché i tipi passati a std::is_base_of devono essere completi.

Non ho prove positive, ma penso che sia imansible. Il seguente errore – per la stessa ragione del tuo tentativo – e penso che sia il più lontano che possiamo ottenere:

 struct Foo { auto self_() -> decltype(*this) { return *this; } using self = decltype(self_()); }; 

In sostanza, ciò che dimostra è che l’ambito in cui vogliamo dichiarare il typedef semplicemente non ha accesso (sia esso diretto o indiretto) a this , e non esiste un altro modo (indipendente dal compilatore) di ottenere il tipo o il nome della class.

Ciò che funziona sia in GCC che in clang è quello di creare un typedef che faccia riferimento a this usando this nel trailing-return-type di una funzione typedef. Poiché questa non è la dichiarazione di una funzione membro statica, l’uso di this è tollerato. È quindi ansible utilizzare quel typedef per definire self .

 #define DEFINE_SELF() \ typedef auto _self_fn() -> decltype(*this); \ using self = decltype(((_self_fn*)0)()) struct Foo { DEFINE_SELF(); }; struct Bar { DEFINE_SELF(); }; 

Sfortunatamente, una lettura rigorosa dello standard dice che anche questo non è valido. Ciò che fa clang è controllare che this non sia usato nella definizione di una funzione membro statica. E qui, in effetti non lo è. GCC non si preoccupa se this viene utilizzato in un tipo di ritorno trailing indipendentemente dal tipo di funzione, lo consente anche per static funzioni membro static . Tuttavia, ciò che lo standard effettivamente richiede è che this non venga usato al di fuori della definizione di una funzione membro non statica (o inizializzatore di membri di dati non statici). Intel ha ragione e rifiuta questo.

Dato che:

  • this è permesso solo in inizializzatori di membri di dati non statici e funzioni di membri non statici ([expr.prim.general] p5),
  • i membri di dati non statici non possono dedurre il loro tipo dall’inizializzatore ([dcl.spec.auto] p5),
  • le funzioni membro non statiche possono essere definite da un nome non qualificato nel contesto di una chiamata di funzione ([expr.ref] p4)
  • le funzioni membro non statiche possono essere chiamate solo da un nome non qualificato, anche in contesti non valutati, quando può essere usato ([over.call.func] p3),
  • un riferimento a una funzione membro non statico tramite nome qualificato o accesso membro richiede un riferimento al tipo definito

Penso di poter dire in modo conclusivo che non c’è assolutamente modo di implementare il self senza includere in qualche modo, da qualche parte, il nome del tipo.

Modifica : C’è un difetto nel mio precedente ragionamento. “Le funzioni membro non statiche possono essere chiamate solo da un nome non qualificato, anche in contesti non valutati, quando può essere usato ([over.call.func] p3),” non è corretto. Quello che dice in realtà è

Se la parola chiave this (9.3.2) è in ambito e si riferisce alla class T , o una class derivata di T , allora l’argomento object implicito è (*this) . Se la parola chiave non è nell’ambito o si riferisce a un’altra class, allora un object inventato di tipo T diventa l’argomento object implicito. Se la lista degli argomenti è aumentata da un object forzato e la risoluzione di sovraccarico seleziona una delle funzioni membro non statiche di T , la chiamata è mal formata.

All’interno di una funzione membro statico, this potrebbe non apparire, ma esiste ancora.

Tuttavia, secondo i commenti, all’interno di una funzione membro statica, la trasformazione di f() in (*this).f() non viene eseguita e non viene eseguita, quindi [expr.call] p1 viene violato:

[…] Per una chiamata di funzione membro, l’espressione postfissa deve essere un accesso implicito (9.3.1, 9.4) o esplicito di un membro della class (5.2.5) di cui […]

in quanto non ci sarebbe alcun accesso ai membri. Quindi anche quello non funzionerebbe.

 #define SELF_CHECK( SELF ) void self_check() { static_assert( std::is_same< typename std::decay::type, SELF >::value, "self wrong type" ); } #define SELF(T) typedef T self; SELF_CHECK(T) struct Foo { SELF(Foo); // works, self is defined as `Foo` }; struct Bar { SELF(Foo); // fails }; 

questo non funziona sui tipi di template, poiché self_check non viene chiamato, quindi static_assert non viene valutato.

Possiamo fare alcuni hack per farlo funzionare anche per i template , ma ha un minor costo di esecuzione.

 #define TESTER_HELPER_TYPE \ template \ struct line_tester_t { \ line_tester_t() { \ static_assert( std::is_same< decltype(T::line_tester), line_tester_t >::value, "test failed" ); \ static_assert( std::is_same< decltype(&T::static_test_zzz), T*(*)() >::value, "test 2 failed" ); \ } \ } #define SELF_CHECK( SELF ) void self_check() { static_assert( std::is_same< typename std::decay::type, SELF >::value, "self wrong type" ); } #define SELF(T) typedef T self; SELF_CHECK(T); static T* static_test_zzz() { return nullptr; }; TESTER_HELPER_TYPE; line_tester_t line_tester 

nella tua class viene creata una struct vuota di dimensione 1 byte. Se il tuo tipo è istanziato, il self viene messo alla prova.

Penso anche che sia imansible, ecco un altro tentativo fallito ma IMHO che evita this -accesso:

 template struct class_t; template struct class_t< R (T::*)() > { using type = T; }; struct Foo { void self_f(); using self = typename class_t::type; }; #include  int main() { static_assert( std::is_same< Foo::self, Foo >::value, "" ); } 

che fallisce perché C ++ richiede di qualificare self_f con la class quando si vuole prendere il suo indirizzo 🙁

Recentemente ho scoperto che *this è permesso in un inizializzatore controvento o uguale . Descritto in § 5.1.1 ( dalla bozza di lavoro n3337 ):

3 [..] Diversamente dall’espressione dell’object in altri contesti, *this non è richiesto che sia di tipo completo per scopi di accesso ai membri della class (5.2.5) al di fuori del corpo della funzione membro. [..]

4 Altrimenti, se un dichiaratore membro dichiara un membro dati non statico (9.2) di una class X, nell’espressione this tratta di un valore di tipo “puntatore a X” all’interno dell’inizializzatore opzionale brace-or-equal . Non deve apparire altrove nel dichiaratore membro .

5 L’espressione non deve apparire in nessun altro contesto. [ Esempio:

 class Outer { int a[sizeof(*this)]; // error: not inside a member function unsigned int sz = sizeof(*this); // OK: in brace-or-equal-initializer void f() { int b[sizeof(*this)]; // OK struct Inner { int c[sizeof(*this)]; // error: not inside a member function of Inner }; } }; 

esempio finale ]

Con questo in mente, il seguente codice:

 struct Foo { Foo* test = this; using self = decltype(test); static void smf() { self foo; } }; #include  #include  int main() { static_assert( std::is_same< Foo::self, Foo* >::value, "" ); } 

supera la static_assert Daniel Frey .

Esempio dal vivo

A meno che il tipo non debba essere un tipo membro della class che racchiude, è ansible sostituire l’uso di self con decltype(*this) . Se lo si utilizza in molti punti del codice, è ansible definire un SELF macro come segue:

 #define SELF decltype(*this) 

Fornire la mia versione. La cosa migliore è che il suo uso è lo stesso della class nativa. Tuttavia, non funziona per le classi template.

 template class Self; #define CLASS(Name) \ class Name##_; \ typedef Self Name; \ template<> class Self CLASS(A) { int i; Self* clone() const { return new Self(*this); } }; CLASS(B) : public A { float f; Self* clone() const { return new Self(*this); } }; 

Basandosi sulla risposta di hvd, ho scoperto che l’unica cosa che mancava era la rimozione del riferimento, ecco perché il controllo std :: is_same fallisce (b / c il tipo risultante è in realtà un riferimento al tipo). Ora questa macro senza parametri può fare tutto il lavoro. Esempio di lavoro di seguito (utilizzo GCC 8.1.1).

 #define DEFINE_SELF \ typedef auto _self_fn() -> std::remove_reference::type; \ using self = decltype(((_self_fn*)0)()) class A { public: DEFINE_SELF; }; int main() { if (std::is_same_v) std::cout << "is A"; } 

Ripeterò l’ovvia soluzione di “doverlo fare da solo”. Questa è la succinta versione C ++ 11 del codice, che funziona sia con classi semplici che con modelli di class:

 #define DECLARE_SELF(Type) \ typedef Type TySelf; /**< @brief type of this class */ \ /** checks the consistency of TySelf type (calling it has no effect) */ \ void self_check() \ { \ static_assert(std::is_same::value, "TySelf is not what it should be"); \ } \ enum { static_self_check_token = __LINE__ }; \ static_assert(int(static_self_check_token) == \ int(TySelf::static_self_check_token), \ "TySelf is not what it should be") 

Puoi vederlo in azione su ideone . La genesi, che porta a questo risultato è qui sotto:

 #define DECLARE_SELF(Type) typedef Type _TySelf; /**< @brief type of this class */ struct XYZ { DECLARE_SELF(XYZ) }; 

Questo ha il problema ovvio con copia-incolla il codice in una class diversa e dimenticando di cambiare XYZ, come qui:

 struct ABC { DECLARE_SELF(XYZ) // !! }; 

Il mio primo approccio non era molto originale: creare una funzione, come questa:

 /** * @brief namespace for checking the _TySelf type consistency */ namespace __self { /** * @brief compile-time assertion (_TySelf must be declared the same as the type of class) * * @tparam _TySelf is reported self type * @tparam _TyDecltypeThis is type of *this */ template  class CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE; /** * @brief compile-time assertion (specialization for assertion passing) * @tparam _TySelf is reported self type (same as type of *this) */ template  class CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE<_TySelf, _TySelf> {}; /** * @brief static assertion helper type * @tparam n_size is size of object being used as assertion message * (if it's a incomplete type, compiler will display object name in error output) */ template  class CStaticAssert {}; /** * @brief helper function for self-check, this is used to derive type of this * in absence of decltype() in older versions of C++ * * @tparam _TyA is reported self type * @tparam _TyB is type of *this */ template  inline void __self_check_helper(_TyB *UNUSED(p_this)) { typedef CStaticAssert)> _TyAssert; // make sure that the type reported as self and type of *this is the same } /** * @def __SELF_CHECK * @brief declares the body of __self_check() function */ #define __SELF_CHECK \ /** checks the consistency of _TySelf type (calling it has no effect) */ \ inline void __self_check() \ { \ __self::__self_check_helper<_tyself>(this); \ } /** * @def DECLARE_SELF * @brief declares _TySelf type and adds code to make sure that it is indeed a correct one * @param[in] Type is type of the enclosing class */ #define DECLARE_SELF(Type) \ typedef Type _TySelf; /**< @brief type of this class */ \ __SELF_CHECK } // ~self 

È piuttosto lungo, ma per favore portami qui. Questo ha il vantaggio di lavorare in C ++ 03 senza decltype , poiché la funzione __self_check_helper viene utilizzata per dedurne il tipo. Inoltre, non esiste static_assert , ma viene invece utilizzato il trucco sizeof() . Potresti renderlo molto più breve per C ++ 0x. Ora questo non funzionerà per i modelli. Inoltre, c'è un problema minore con la macro che non si aspetta un punto e virgola alla fine, se si compila con pedantic, si lamenterà di un punto e virgola inutile (o rimarrai con una macro dall'aspetto strano che non termina con punto e virgola nel corpo di XYZ e ABC ).

Effettuare un controllo sul Type passato a DECLARE_SELF non è un'opzione, in quanto verrebbe solo controllato la class XYZ (che è ok), ignaro di ABC (che ha un errore). E poi mi ha colpito. Una soluzione senza costi aggiuntivi per lo storage che funziona con i modelli:

 namespace __self { /** * @brief compile-time assertion (_TySelf must be declared the same as the type of class) * @tparam b_check is the asserted value */ template  class CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE2; /** * @brief compile-time assertion (specialization for assertion passing) */ template <> class CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE2 {}; /** * @def DECLARE_SELF * @brief declares _TySelf type and adds code to make sure that it is indeed a correct one * @param[in] Type is type of the enclosing class */ #define DECLARE_SELF(Type) \ typedef Type _TySelf; /**< @brief type of this class */ \ __SELF_CHECK \ enum { __static_self_check_token = __LINE__ }; \ typedef __self::CStaticAssert)> __static_self_check } // ~__self 

Ciò semplifica semplicemente l'asserzione statica su un valore enum univoco (o almeno univoco nel caso in cui non si scriva tutto il codice su una singola riga), non viene utilizzato alcun trucco di comparazione dei tipi e funziona come asserzione statica, anche nei modelli . E come bonus - ora è richiesto il punto e virgola finale :).

Vorrei ringraziare Yakk per avermi dato una buona ispirazione. Non scriverei questo senza prima vedere la sua risposta.

Testato con VS 2008 e g ++ 4.6.3. In effetti, con l'esempio XYZ e ABC , si lamenta:

 [email protected]:~$ g++ self.cpp -c -o self.o self.cpp:91:5: error: invalid application of âsizeofâ to incomplete type â__self::CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE2â self.cpp:91:5: error: template argument 1 is invalid self.cpp: In function âvoid __self::__self_check_helper(_TyB*) [with _TyA = XYZ, _TyB = ABC]â: self.cpp:91:5: instantiated from here self.cpp:58:87: error: invalid application of âsizeofâ to incomplete type â__self::CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPEâ 

Ora se rendiamo ABC un modello:

 template  struct ABC { DECLARE_SELF(XYZ); // line 92 }; int main(int argc, char **argv) { ABC abc; return 0; } 

Otterremo:

 [email protected]:~$ g++ self.cpp -c -o self.o self.cpp: In instantiation of âABCâ: self.cpp:97:18: instantiated from here self.cpp:92:9: error: invalid application of âsizeofâ to incomplete type â__self::CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE2â 

È stato triggersto solo il controllo del numero di riga, poiché il controllo funzionale non è stato compilato (come previsto).

Con C ++ 0x (e senza i caratteri di sottolineatura diabolici), è necessario solo:

 namespace self_util { /** * @brief compile-time assertion (tokens in class and TySelf must match) * @tparam b_check is the asserted value */ template  class SELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE; /** * @brief compile-time assertion (specialization for assertion passing) */ template <> class SELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE {}; /** * @brief static assertion helper type * @tparam n_size is size of object being used as assertion message * (if it's a incomplete type, compiler will display object name in error output) */ template  class CStaticAssert {}; #define SELF_CHECK \ /** checks the consistency of TySelf type (calling it has no effect) */ \ void self_check() \ { \ static_assert(std::is_same::value, "TySelf is not what it should be"); \ } #define DECLARE_SELF(Type) \ typedef Type TySelf; /**< @brief type of this class */ \ SELF_CHECK \ enum { static_self_check_token = __LINE__ }; \ typedef self_util::CStaticAssert)> static_self_check } // ~self_util 

Credo che il bit CStaticAssert sia purtroppo ancora necessario in quanto produce un tipo, che viene tipizzato nel corpo del modello (suppongo che lo stesso non possa essere fatto con static_assert ). Il vantaggio di questo approccio è ancora il suo costo zero.

Non so tutto su questi modelli stravaganti, che ne dici di qualcosa di super-semplice:

 #define DECLARE_TYPEOF_THIS typedef CLASSNAME typeof_this #define ANNOTATED_CLASSNAME(DUMMY) CLASSNAME #define CLASSNAME X class ANNOTATED_CLASSNAME (X) { public: DECLARE_TYPEOF_THIS; CLASSNAME () { moi = this; } ~CLASSNAME () { } typeof_this *moi; // ... }; #undef CLASSNAME #define CLASSNAME Y class ANNOTATED_CLASSNAME (Y) { // ... }; #undef CLASSNAME 

Lavoro fatto, a meno che tu non possa sopportare un paio di macro. Puoi anche usare CLASSNAME per dichiarare i tuoi costruttori (e, naturalmente, il distruttore).

Demo dal vivo