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


101

In C ++ manca l'equivalente della parola chiave PHPself , che valuta il tipo della classe che lo racchiude.

È abbastanza facile falsificarlo per classe:

struct Foo
{
   typedef Foo self;
};

ma ho dovuto scrivere di Foonuovo. Forse un giorno sbaglierò questo e causerò un bug silenzioso.

Posso usare una combinazione di decltypee amici per fare questo lavoro "in modo autonomo"? Ho già provato quanto segue ma thisnon è 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 di static, che fa lo stesso ma con l'associazione tardiva.)


9
this_tsarebbe probabilmente più allineato con i normali nomi C ++.
Bartek Banachewicz

3
@BartekBanachewicz: or this_type
PlasmaHH

10
@Praetorian, non ricordo se era una proposta o meno, ma qualcuno ha suggerito auto()e ~auto()per medici / medici. Interessante per non dire altro. Se usato per quello scopo, forse typedef auto self;, ma mi sembra un po 'impreciso.
chris

11
Onestamente, se avessi intenzione di suggerire la sintassi per renderlo possibile, probabilmente lo sarebbe decltype(class), forse con un decltype(struct)equivalente. È molto più chiaro che autoin un contesto specifico e non vedo alcun problema che si adatti al linguaggio basato su decltype(auto).
chris

11
Dal momento che vuoi evitare errori, puoi impostare una funzione membro fittizio con static_assert, come void _check() { static_assert(std::is_same<self&, decltype(*this)>::value, "Correct your self type"); }Non funziona con i modelli di classe, però ...
milleniumbug

Risposte:


38

Ecco come puoi farlo senza ripetere il tipo di Foo:

template <typename...Ts>
class Self;

template <typename X, typename...Ts>
class Self<X,Ts...> : public Ts...
{
protected:
    typedef X self;
};

#define WITH_SELF(X) X : public Self<X>
#define WITH_SELF_DERIVED(X,...) X : public Self<X,__VA_ARGS__>

class WITH_SELF(Foo)
{
    void test()
    {
        self foo;
    }
};

Se vuoi derivare da Fooallora dovresti usare la macro WITH_SELF_DERIVEDnel modo seguente:

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

Puoi persino eseguire l'ereditarietà multipla con tutte le classi base che desideri (grazie ai modelli variadici e alle macro variadiche):

class WITH_SELF(Foo2)
{
    /* ... */
};

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

Ho verificato che funzioni su gcc 4.8 e clang 3.4.


18
Immagino che la risposta sia "no, ma Ralph può!" ;)
Gare di leggerezza in orbita il

3
In che modo questo è in qualche modo superiore al semplice inserimento del typedef? E dio, perché avresti bisogno del typedef? Perché?
Miglia in rotta

7
@MilesRout Questa è una domanda sulla domanda, non sulla risposta. In molti casi nello sviluppo del software (e soprattutto nella manutenzione) è utile evitare ridondanze nel codice, in modo che cambiare qualcosa in un punto non richieda di cambiare il codice in un altro posto. Questo è il punto centrale di autoe decltypeo in questo caso di self.
Ralph Tandetzky

1
template<typename T>class Self{protected: typedef T self;}; class WITH_SELF(Foo) : public Bar, private Baz {};sarebbe stato più semplice e avrebbe consentito un controllo più preciso sull'eredità - qualche motivo contro?
Aconcagua

@mmmmmmmm, se non hai imparato ad apprezzare profondamente il principio "Don't Repeat Yourself", è probabile che tu non abbia ancora codificato abbastanza / seriamente. Questo "disordine" (tutt'altro, in realtà) è una soluzione piuttosto elegante nel contesto del parlare di una caratteristica del linguaggio inelegante (o di una funzione errata, persino di una mancanza di alcune misure rigorose).
Sz.

38

Una possibile soluzione alternativa (dato che devi ancora scrivere il tipo una volta):

template<typename T>
struct Self
{
protected:
    typedef T self;
};

struct Foo : public Self<Foo>
{
    void test()
    {
        self obj;
    }
};

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

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

Si noti che una funzione static_assertall'interno di un membro è probabilmente l'unico modo per controllare, poiché i tipi passati std::is_base_ofdevono essere completi.


4
Non c'è bisogno di typenamenel typedef. E poiché questo non riduce il numero di licenziamenti, non credo che sia una valida alternativa.
Konrad Rudolph

Ha esattamente lo stesso problema di ripetere il Foonome.
Bartek Banachewicz

6
Si è marginalmente migliore rispetto all'approccio originale, anche se, dal momento che la ripetizione è molto vicini tra loro. Non una soluzione alla domanda, ma +1 per un degno tentativo di una soluzione alternativa.
Gare di leggerezza in orbita

4
Ho usato quella soluzione un paio di volte, e ha una cosa CATTIVA: quando in seguito derivi da Foo, devi: (1) propagare la T verso l'alto alla foglia-discendente, o (2) ricordarti di ereditare da SelfT molte volte , o (3) accetta che tutti i bambini siano la Base .. utilizzabile, ma poco carina.
quetzalcoatl

@quetzalcoatl: Dal momento che sto cercando di replicare selfpiuttosto che static, non c'è problema.
Gare di leggerezza in orbita

33

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

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

E poi usa like

CLASS_WITH_SELF(Foo) 
};

#define END_CLASS }; probabilmente aiuterebbe la leggibilità.


Potresti anche prendere @ Paranaix Selfe usarlo (inizia a diventare davvero hacker)

#define WITH_SELF(X) X : public Self<X>

class WITH_SELF(Foo) {
};

18
EWWWW END_CLASS. È totalmente inutile.
Cucciolo

31
@DeadMG Penso che ad alcune persone potrebbe piacere una maggiore coerenza; dopotutto, il primo utilizzo della macro non finisce con {, quindi }è "sospeso", cosa che probabilmente non piacerà anche agli editor di testo.
Bartek Banachewicz

6
Bella idea, ma anche se non sono fondamentalmente contrario alle macro, accetterei il suo utilizzo qui solo se imitasse lo scoping C ++, cioè se fosse utilizzabile come CLASS_WITH_SELF(foo) { … };- e penso che sia impossibile da ottenere.
Konrad Rudolph

2
@KonradRudolph ho aggiunto anche un modo per farlo. Non che mi piaccia, solo per completezza
Bartek Banachewicz

1
Tuttavia, ci sono alcuni problemi con questo approccio. Il primo non ti consente di far ereditare facilmente la classe (a meno che tu non usi un altro parametro / i macro), e il secondo ha tutti i problemi di ereditare da esso che Selfha.
Bartek Banachewicz

31

Non ho prove positive ma penso che sia impossibile. Quanto segue fallisce, per lo stesso motivo del tuo tentativo, e penso che sia il massimo che possiamo ottenere:

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

    using self = decltype(self_());
};

In sostanza, ciò che questo dimostra è che l'ambito in cui vogliamo dichiarare il nostro typedef semplicemente non ha accesso (diretto o indiretto) a this, e non c'è altro modo (indipendente dal compilatore) per raggiungere il tipo o il nome della classe.


4
Questo sarà probabilmente con la detrazione del tipo di ritorno di C ++ 1y?
giorno

4
@dyp Ai fini della mia risposta, ciò non cambierà nulla. L'errore qui non è nel tipo di ritorno finale, è nella chiamata.
Konrad Rudolph

1
@quetzalcoatl: L'interno di decltypeè un contesto non valutato, quindi invocare la funzione membro non è il problema (che non verrà tentato)
Gare di leggerezza in orbita

1
@TomKnapen Provalo con clang e fallirà. Il fatto che sia accettato da GCC è un bug, per quanto ne so.

4
FWIW, struct S { int i; typedef decltype(i) Int; };funziona anche se iè un membro dati non statico. Funziona perché decltypeha un'eccezione speciale in cui un nome semplice non viene valutato come espressione. Ma non riesco a pensare a un modo per utilizzare questa possibilità in modo da rispondere alla domanda.

21

Ciò che funziona sia in GCC che in clang è creare un typedef a cui si fa riferimento thisutilizzando thisil trailing-return-type di una funzione typedef. Poiché questa non è la dichiarazione di una funzione membro statica, l'uso di thisè tollerato. È quindi possibile 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. Quello che fa clang è controllare che thisnon sia usato nella definizione di una funzione membro statica. E qui, in effetti non lo è. GCC non si preoccupa se thisviene utilizzato in un tipo di ritorno finale indipendentemente dal tipo di funzione, lo consente anche per le staticfunzioni membro. Tuttavia, ciò che lo standard richiede effettivamente è che thisnon venga utilizzato al di fuori della definizione di una funzione membro non statica (o inizializzatore di membri dati non statici). Intel ha ragione e lo rifiuta.

Dato che:

  • this è consentito solo in inizializzatori di membri dati non statici e funzioni membro non statici ([expr.prim.general] p5),
  • i membri di dati non statici non possono avere il loro tipo dedotto dall'inizializzatore ([dcl.spec.auto] p5),
  • È possibile fare riferimento alle funzioni membro non statiche solo con un nome non qualificato nel contesto di una chiamata di funzione ([expr.ref] p4)
  • le funzioni membro non statiche possono essere chiamate solo con un nome non qualificato, anche in contesti non valutati, quando thispossono essere utilizzate ([over.call.func] p3),
  • un riferimento a una funzione membro non statica tramite un nome completo o l'accesso al membro richiede un riferimento al tipo da definire

Penso di poter definitivamente affermare che non c'è modo di implementare selfsenza 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 con un nome non qualificato, anche in contesti non valutati, quando questo può essere utilizzato ([over.call.func] p3)", non è corretto. Quello che in realtà dice è

Se la parola chiave this(9.3.2) è nell'ambito e si riferisce alla classe T, oa una classe derivata di T, l'argomento dell'oggetto implicito è (*this). Se la parola chiave thisnon è nell'ambito o si riferisce a un'altra classe, un oggetto inventato di tipo Tdiventa l'argomento dell'oggetto implicito. Se l'elenco degli argomenti è aumentato da un oggetto inventato e la risoluzione dell'overload seleziona una delle funzioni membro non statiche di T, la chiamata è mal formata.

All'interno di una funzione membro statica, thispotrebbe non apparire, ma esiste ancora.

Tuttavia, secondo i commenti, all'interno di una funzione membro statica, la trasformazione di f()a (*this).f()non sarebbe stata eseguita, e se non viene eseguita, allora [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 al membro di classe (5.2.5) il cui [...]

poiché non ci sarebbe l'accesso dei membri. Quindi anche quello non funzionerebbe.


Penso che [class.mfct.non-static] / 3 dica che _self_fn_1()viene "trasformato" in (*this)._self_fn_1(). Non sono sicuro che ciò lo renda illegale, però.
giorno

@dyp Dice "viene utilizzato in un membro della classe Xin un contesto in cui thispuò essere utilizzato", quindi non penso che la trasformazione venga eseguita.

1
Ma allora non è né un accesso implicito né esplicito ai membri della classe ..? [expr.call] / 1 "Per una chiamata di funzione membro, l'espressione postfissa deve essere un accesso implicito o esplicito al membro di una classe [...]"
dyp

(Voglio dire, cosa succede quando lo hai auto _self_fn_1() -> decltype(*this); auto _self_fn_1() const -> decltype(*this);?)
dyp

@dyp [expr.call] / 1 è un buon punto, dovrò dare un'occhiata più da vicino. A proposito di constsovraccarichi, però: non è un problema. 5.1p3 è stato specificamente modificato per applicarsi anche alle funzioni membro statiche e dice che il tipo di thisè Foo*/ Bar*(senza const), perché non c'è constnella dichiarazione di _self_fn_2.

17
#define SELF_CHECK( SELF ) void self_check() { static_assert( std::is_same< typename std::decay<decltype(*this)>::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 modello, poiché self_checknon viene chiamato, quindi static_assertnon viene valutato.

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

#define TESTER_HELPER_TYPE \
template<typename T, std::size_t line> \
struct line_tester_t { \
  line_tester_t() { \
    static_assert( std::is_same< decltype(T::line_tester), line_tester_t<T,line> >::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<decltype(*this)>::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<T,__LINE__> line_tester

structnella classe viene creato un vuoto di dimensione 1 byte. Se il tuo tipo è istanziato, selfviene testato contro.


Neanche questo è male!
Gare di leggerezza in orbita

@LightnessRacesinOrbit ora con templateopzioni di supporto di classe.
Yakk - Adam Nevraumont

Stavo pensando esattamente a questo ieri mentre uscivo dal lavoro. Mi hai battuto sul tempo :). Suggerirei di dichiarare self_check () come inline, per evitare problemi di collegamento (stesso simbolo Foo :: self_check () trovato in più file oggetto).
il suino

1
@theswine: 9.3 / 2 è l'indice di un paragrafo nello standard C ++, che garantisce che le funzioni di membro di classe definite nel corpo della definizione di classe sono già, implicitamente, inline. Ciò significa che non è necessario scrivere inlineaffatto. Quindi, se hai scritto inlinedavanti a ogni definizione di funzione di questo membro della classe per tutta la tua carriera, puoi fermarti ora;)
Gare di leggerezza in orbita

2
@LightnessRacesinOrbit Oh, in realtà lo ero. Grazie, questo mi farà risparmiare un po 'di digitazione in futuro :). Sono sempre stupito da quanto non so di C ++.
il suino

11

Penso anche che sia impossibile, ecco un altro tentativo fallito ma IMHO interessante che evita l' thisaccesso-:

template<typename T>
struct class_t;

template<typename T, typename R>
struct class_t< R (T::*)() > { using type = T; };

struct Foo
{
   void self_f(); using self = typename class_t<decltype(&self_f)>::type;
};

#include <type_traits>

int main()
{
    static_assert( std::is_same< Foo::self, Foo >::value, "" );
}

che fallisce perché C ++ richiede di qualificarti self_fcon la classe quando vuoi prendere il suo indirizzo :(


E lo stesso problema si verifica con un int T::*puntatore regolare alla variabile membro. E int self_var; typedef decltype(&self_var) self_ptrnon funziona neanche, è solo un normale int*.
MSalters

9

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

3 [..] A differenza dell'espressione oggetto in altri contesti, *thisnon è richiesto che sia di tipo completo ai fini dell'accesso ai membri della classe (5.2.5) al di fuori del corpo della funzione membro. [..]

4 Altrimenti, se un membro-dichiaratore dichiara un membro di dati non statici (9.2) di una classe X, l'espressione thisè un prvalue di tipo "puntatore a X" all'interno dell'inizializzatore opzionale di parentesi graffa o uguale . Non apparirà altrove nel membro-dichiarante .

5 L'espressione thisnon 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 codice seguente:

struct Foo
{
    Foo* test = this;
    using self = decltype(test);

    static void smf()
    {
        self foo;
    }
};

#include <iostream>
#include <type_traits>

int main()
{
    static_assert( std::is_same< Foo::self, Foo* >::value, "" );
}

supera Daniel Frey static_assert .

Live example


testTuttavia, hai una fastidiosa variabile inutile
MM

@ Matt True, ma l'ho trovato comunque interessante.

1
Questo avrebbe potuto funzionare senza = this, giusto? E perché non solousing self = Foo*;
user362515

1
Non abbiamo guadagnare qualcosa qui sicuramente, perché abbiamo avuto a dichiarare testdi essere di tipo, um, Foo *!
Paul Sanders

4

A meno che il tipo non debba essere il tipo di membro della classe che lo racchiude, è possibile sostituire l'uso di selfcon decltype(*this). Se lo utilizzi in molti punti del codice, puoi definire una macro SELFcome segue:

#define SELF decltype(*this)

2
E non puoi usarlo al di fuori della classe o in classi nidificate
Drax

1
@Drax: non dovrebbe essere disponibile al di fuori della classe.
Ben Voigt

@BenVoigt Ma si suppone che sia disponibile in classi annidate che è IMO il caso d'uso più interessante.
Drax

1
Non credo proprio. Non dovrebbe fare selfriferimento alla classe immediatamente inclusiva e non alla classe esterna? Ma non conosco molto bene php.
Ben Voigt

1
@ LightnessRacesinOrbit: immagino che il codice e l'errore dovrebbero dire "PHP non ha tipi nidificati"?
Ben Voigt

1

Fornisci la mia versione. La cosa migliore è che il suo utilizzo è lo stesso della classe nativa. Tuttavia, non funziona per le classi modello.

template<class T> class Self;

#define CLASS(Name) \
class Name##_; \
typedef Self<Name##_> Name; \
template<> class Self<Name##_>

CLASS(A)
{
    int i;
    Self* clone() const { return new Self(*this); }
};

CLASS(B) : public A
{
    float f;
    Self* clone() const { return new Self(*this); }
};

1

Basandosi sulla risposta di hvd, ho scoperto che l'unica cosa che mancava era rimuovere il 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 (io uso GCC 8.1.1).

#define DEFINE_SELF \
    typedef auto _self_fn() -> std::remove_reference<decltype(*this)>::type; \
    using self = decltype(((_self_fn*)0)())

class A {
    public:
    DEFINE_SELF;
};

int main()
{
    if (std::is_same_v<A::self, A>)
        std::cout << "is A";
}

Non si compila su altri compilatori oltre a GCC.
zedu

0

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

#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<decltype(*((TySelf*)(0))), \
            decltype(*this)>::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 ha portato a questo risultato è di seguito:

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

struct XYZ {
    DECLARE_SELF(XYZ)
};

Questo ha l'ovvio problema di copiare e incollare il codice in una classe diversa e dimenticare di modificare XYZ, come qui:

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

Il mio primo approccio non è stato 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 <tt>*this</tt>
 */
template <class _TySelf, class _TyDecltypeThis>
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 <tt>*this</tt>)
 */
template <class _TySelf>
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 <const size_t n_size>
class CStaticAssert {};

/**
 *  @brief helper function for self-check, this is used to derive type of this
 *      in absence of <tt>decltype()</tt> in older versions of C++
 *
 *  @tparam _TyA is reported self type
 *  @tparam _TyB is type of <tt>*this</tt>
 */
template <class _TyA, class _TyB>
inline void __self_check_helper(_TyB *UNUSED(p_this))
{
    typedef CStaticAssert<sizeof(CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE<_TyA, _TyB>)> _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

È un po 'lungo, ma per favore abbi pazienza qui. Questo ha il vantaggio di lavorare in C ++ 03 senza decltype, poiché la __self_check_helperfunzione viene impiegata per dedurre il tipo di this. Inoltre, non c'è static_assert, ma il sizeof()trucco è invece impiegato. 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 il punto e virgola alla fine, se si compila con pedante, si lamenterà di un punto e virgola non necessario in più (o rimarrà con una macro dall'aspetto strano che non termina con punto e virgola nel corpo di XYZe ABC).

Fare un controllo su Typeciò che è passato a DECLARE_SELFnon è un'opzione, in quanto ciò controllerebbe solo la XYZclasse (che è ok), ignara di ABC(che ha un errore). E poi mi ha colpito. Una soluzione a costo zero di archiviazione senza aggiunta 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 <bool b_check>
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<true> {};

/**
 *  @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<sizeof(CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE2<int(__static_self_check_token) == int(_TySelf::__static_self_check_token)>)> __static_self_check

} // ~__self 

Questo rende semplicemente un'asserzione statica su un valore enum univoco (o almeno univoco nel caso in cui non scrivi tutto il codice su una singola riga), non viene impiegato alcun trucco di confronto 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 lo scriverei senza prima vedere la sua risposta.

Testato con VS 2008 e g ++ 4.6.3. Infatti, con l' esempio XYZe ABC, si lamenta:

ipolok@ivs:~$ 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<false>â
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<XYZ, ABC

Ora, se creiamo ABC un modello:

template <class X>
struct ABC {
    DECLARE_SELF(XYZ); // line 92
};

int main(int argc, char **argv)
{
    ABC<int> abc;
    return 0;
}

Otterremo:

ipolok@ivs:~$ g++ self.cpp -c -o self.o
self.cpp: In instantiation of âABC<int>â:
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<false>â

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

Con C ++ 0x (e senza i malvagi trattini bassi), avresti bisogno solo di:

namespace self_util {

/**
 *  @brief compile-time assertion (tokens in class and TySelf must match)
 *  @tparam b_check is the asserted value
 */
template <bool b_check>
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<true> {};

/**
 *  @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 <const size_t n_size>
class CStaticAssert {};

#define SELF_CHECK \
    /** checks the consistency of TySelf type (calling it has no effect) */ \
    void self_check() \
    { \
        static_assert(std::is_same<TySelf, decltype(*this)>::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<sizeof(SELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE<int(static_self_check_token) == int(TySelf::static_self_check_token)>)> static_self_check

} // ~self_util

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


Stai essenzialmente reimplementando static_assertqui, non è vero? Inoltre, il tuo codice completo non è valido perché stai utilizzando identificatori illegali (riservati).
Konrad Rudolph

@KonradRudolph Sì, è proprio così. Non ho C ++ 0x al momento, quindi ho reimplementato static_assert per fornire una risposta completa. Lo dico nella risposta. Non è valido? Puoi indicare come? È stato compilato bene, lo sto usando adesso.
il suino

1
Gli identificatori non sono validi perché C ++ riserva tutto con un trattino basso iniziale seguito da una lettera maiuscola, oltre a due trattini bassi iniziali nell'ambito globale, per il compilatore. Il codice utente non deve utilizzarlo, ma non tutti i compilatori lo contrassegneranno come errore.
Konrad Rudolph,

@KonradRudolph Vedo, non lo sapevo. Ho un sacco di codice che lo usa, non ho mai avuto problemi con esso né su Linux / Mac / Windows. Ma credo sia bene saperlo.
il suino

0

Non so tutto di questi modelli stravaganti, che ne dici di qualcosa di semplicissimo:

#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 sopporti un paio di macro. Puoi anche usare CLASSNAMEper dichiarare i tuoi costruttori (e, ovviamente, distruttore).

Demo dal vivo .


1
Ha un effetto abbastanza pronunciato su come la classe può / deve essere utilizzata in seguito
Lightness Races in Orbit

@LightnessRacesinOrbit In che modo? Non lo vedo Riflettendoci, ho rimosso l'ultima frase del mio post originale. Quello che avevo lì in origine potrebbe averti portato a pensarlo.
Paul Sanders
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.