Kann ich in C ++ einen autonomen Selbstmitgliedertyp implementieren?

101

In C ++ fehlt das Äquivalent zum PHP- selfSchlüsselwort , das den Typ der einschließenden Klasse ergibt.

Es ist einfach genug, es pro Klasse zu fälschen:

struct Foo
{
   typedef Foo self;
};

aber ich musste Foonochmal schreiben . Vielleicht verstehe ich das eines Tages falsch und verursache einen stillen Fehler.

Kann ich eine Kombination von decltypeund Freunden verwenden, um diese Arbeit "autonom" zu machen? Ich habe bereits Folgendes versucht , thisist aber an dieser Stelle nicht gültig:

struct Foo
{
   typedef decltype(*this) self;
};

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

(Ich werde mir keine Sorgen um das Äquivalent von machen static, was dasselbe tut, aber mit später Bindung.)

Leichtigkeitsrennen im Orbit
quelle
9
this_twäre wahrscheinlich eher auf die reguläre C ++ - Benennung ausgerichtet.
Bartek Banachewicz
3
@BartekBanachewicz: oder this_type
PlasmaHH
10
@Praetorian, ich kann mich nicht erinnern, ob es ein Vorschlag war oder nicht, aber jemand schlug vor auto()und ~auto()für ctors / dtors. Interessant, um es gelinde auszudrücken. Wenn für diesen Zweck vielleicht verwendet typedef auto self;, aber das scheint mir ein bisschen skizzenhaft.
Chris
11
Ehrlich gesagt, wenn ich Syntax vorschlagen würde, um dies zu ermöglichen, wäre es wahrscheinlich decltype(class), vielleicht mit einem decltype(struct)Äquivalent. Das ist viel klarer als nur autoin einem bestimmten Kontext und ich sehe keine Probleme damit, dass es in die Sprache passt, auf der es basiert decltype(auto).
Chris
11
Da Sie Fehler vermeiden möchten, können Sie mit static_assert eine Dummy-Member-Funktion einrichten, z. B. void _check() { static_assert(std::is_same<self&, decltype(*this)>::value, "Correct your self type"); }Funktioniert nicht mit Klassenvorlagen ...
Milleniumbug

Antworten:

38

So können Sie es tun, ohne die Art von Foo zu wiederholen:

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;
    }
};

Wenn Sie ableiten möchten Foo sollten Sie das Makro WITH_SELF_DERIVEDfolgendermaßen verwenden:

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

Sie können sogar mehrere Vererbungen mit so vielen Basisklassen durchführen, wie Sie möchten (dank variadischer Vorlagen und variadischer Makros):

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

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

Ich habe dies überprüft, um auf gcc 4.8 und clang 3.4 zu funktionieren.

Ralph Tandetzky
quelle
18
Ich denke die Antwort ist "nein, aber Ralph kann!" ;)
Leichtigkeitsrennen im Orbit
3
Wie ist das in irgendeiner Weise besser als das einfache Einfügen des Typedef? Und Gott, warum brauchst du überhaupt den Typedef? Warum?
Miles Rout
7
@MilesRout Dies ist eine Frage zur Frage, nicht zur Antwort. In vielen Fällen ist es bei der Softwareentwicklung (und insbesondere bei der Wartung) hilfreich, Redundanzen im Code zu vermeiden, sodass Sie den Code nicht an einem anderen Ort ändern müssen, um etwas an einem Ort zu ändern. Das ist der springende Punkt von autound decltypeoder in diesem Fall von self.
Ralph Tandetzky
1
template<typename T>class Self{protected: typedef T self;}; class WITH_SELF(Foo) : public Bar, private Baz {};wäre einfacher gewesen und würde eine genauere Kontrolle über die Vererbung ermöglichen - irgendwelche Gründe dagegen?
Aconcagua
@mmmmmmmm, wenn Sie nicht gelernt haben, das Prinzip "Wiederholen Sie sich nicht" zutiefst zu schätzen, haben Sie wahrscheinlich noch nicht genug / ernsthaft codiert. Diese "Unordnung" (eigentlich weit davon entfernt) ist eine ziemlich elegante Lösung im Zusammenhang mit der Rede von einem uneleganten Sprachmerkmal (oder einer Fehlfunktion, sogar einem Mangel durch einige strenge Maßnahmen).
Gr.
38

Eine mögliche Problemumgehung (da Sie den Typ noch einmal schreiben müssen):

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

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

Für eine sicherere Version könnten wir sicherstellen, dass sich TFolgendes ergibt Self<T>:

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

Beachten Sie, dass eine static_assertFunktion innerhalb eines Mitglieds wahrscheinlich die einzige Möglichkeit ist, dies zu überprüfen, da übergebene Typen std::is_base_ofvollständig sein müssen.

Sebastian Hoffmann
quelle
4
Keine Notwendigkeit für typenamedas typedef. Und da dies die Anzahl der Redundanzen nicht verringert, halte ich es nicht für eine praktikable Alternative.
Konrad Rudolph
Es hat genau das gleiche Problem, FooNamen zu wiederholen .
Bartek Banachewicz
6
Es ist jedoch geringfügig besser als der ursprüngliche Ansatz, da die Wiederholung sehr nahe beieinander liegt. Keine Lösung für die Frage, aber +1 für einen würdigen Versuch einer bestmöglichen Problemumgehung.
Leichtigkeitsrennen im Orbit
4
Ich habe diese Lösung ein paar Mal verwendet, und sie hat eine SCHLECHTE Sache: Wenn Sie sie später ableiten Foo, müssen Sie entweder: (1) das T nach oben zum Blattnachkommen verbreiten oder (2) daran denken, viele Male von SelfT zu erben , oder (3) akzeptieren, dass alle Kinder etwas sind, um die Basis zu sein .. brauchbar, aber unpretty.
Quetzalcoatl
@quetzalcoatl: Da ich selfeher versuche zu replizieren als static, ist das kein Problem.
Leichtigkeitsrennen im Orbit
33

Sie können anstelle einer regulären Klassendeklaration ein Makro verwenden, das dies für Sie erledigt.

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

Und dann wie verwenden

CLASS_WITH_SELF(Foo) 
};

#define END_CLASS }; würde wahrscheinlich die Lesbarkeit verbessern.


Sie können auch @ Paranaix's nehmen Selfund es verwenden (es wird langsam richtig hackisch)

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

class WITH_SELF(Foo) {
};
Bartek Banachewicz
quelle
18
EWWWW END_CLASS. Das ist völlig unnötig.
Welpe
31
@DeadMG Ich denke, einige Leute mögen vielleicht mehr Konsistenz. Immerhin endet die erste Makro Verwendung nicht mit {, so dass die }„hängende“ ist, die Texteditoren wahrscheinlich, würden auch nicht mögen.
Bartek Banachewicz
6
Gute Idee, aber obwohl ich nicht grundsätzlich gegen Makros bin, würde ich ihre Verwendung hier nur akzeptieren, wenn es das C ++ - Scoping nachahmt, dh wenn es als verwendbar wäre CLASS_WITH_SELF(foo) { … };- und ich denke, das ist unmöglich zu erreichen.
Konrad Rudolph
2
@KonradRudolph Ich habe auch eine Möglichkeit hinzugefügt, dies zu tun. Nicht, dass es mir gefällt, nur der Vollständigkeit halber
Bartek Banachewicz
1
Es gibt jedoch einige Probleme mit diesem Ansatz. Erste erlaubt es nicht , die Klasse erben leicht zu machen (es sei denn , Sie einen anderen Makroparameter (n) verwenden), und zweitens hat alle Probleme der von vererben es , das Selfhat.
Bartek Banachewicz
31

Ich habe keine positiven Beweise, aber ich denke, es ist unmöglich. Folgendes schlägt fehl - aus dem gleichen Grund wie Ihr Versuch - und ich denke, das ist das weiteste, was wir bekommen können:

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

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

Dies zeigt im Wesentlichen, dass der Bereich, in dem wir unser typedef deklarieren möchten, einfach keinen Zugriff (sei es direkt oder indirekt) thishat und es keine andere (vom Compiler unabhängige) Möglichkeit gibt, zum Typ oder Namen der Klasse zu gelangen.

Konrad Rudolph
quelle
4
Wird dies möglicherweise mit dem Abzug des Rückgabetyps von C ++ 1y geschehen?
Dyp
4
@dyp Für meine Antwort ändert das nichts. Der Fehler liegt hier nicht im nachfolgenden Rückgabetyp, sondern im Aufruf.
Konrad Rudolph
1
@quetzalcoatl: Die Innereien von decltypeist ein nicht bewerteter Kontext, so dass das Aufrufen der Mitgliedsfunktion nicht das Problem ist (das nicht versucht wird)
Leichtigkeitsrennen im Orbit
1
@ TomKnapen Versuchen Sie es mit Klirren, und es wird fehlschlagen. Die Tatsache, dass es von GCC akzeptiert wird, ist meines Wissens ein Fehler.
4
FWIW struct S { int i; typedef decltype(i) Int; };funktioniert, obwohl ies sich um ein nicht statisches Datenelement handelt. Es funktioniert, weil decltypees eine spezielle Ausnahme gibt, bei der ein einfacher Name nicht als Ausdruck ausgewertet wird. Aber ich kann mir keine Möglichkeit vorstellen, diese Möglichkeit so zu nutzen, dass die Frage beantwortet wird.
21

Sowohl in GCC als auch in clang funktioniert das Erstellen eines Typedef, auf das verwiesen wird, thisindem thisim Trailing-Return-Typ einer Funktion typedef verwendet wird. Da dies nicht die Deklaration einer statischen Elementfunktion ist, wird die Verwendung von thistoleriert. Sie können dieses typedef dann zum Definieren verwenden self.

#define DEFINE_SELF() \
    typedef auto _self_fn() -> decltype(*this); \
    using self = decltype(((_self_fn*)0)())

struct Foo {
    DEFINE_SELF();
};

struct Bar {
    DEFINE_SELF();
};

Leider besagt eine strikte Lektüre des Standards, dass auch dies nicht gültig ist. Clang überprüft, ob thises bei der Definition einer statischen Elementfunktion nicht verwendet wird. Und hier ist es tatsächlich nicht. GCC macht es nichts aus, wenn thises in einem Trailing-Return-Typ verwendet wird, unabhängig von der Art der Funktion, es erlaubt es sogar fürstatic Mitgliedsfunktionen. Was der Standard jedoch tatsächlich verlangt, ist dasthis nicht außerhalb der Definition einer nicht statischen Elementfunktion (oder eines nicht statischen Datenelementinitialisierers) verwendet wird. Intel macht es richtig und lehnt dies ab.

Vorausgesetzt, dass:

  • this ist nur in nicht statischen Datenelementinitialisierern und nicht statischen Elementfunktionen zulässig ([expr.prim.general] p5),
  • Nicht statische Datenelemente können ihren Typ nicht aus dem Initialisierer ([dcl.spec.auto] p5) ableiten lassen.
  • Nicht statische Elementfunktionen können nur im Rahmen eines Funktionsaufrufs mit einem nicht qualifizierten Namen bezeichnet werden ([expr.ref] p4).
  • Nicht statische Elementfunktionen thiskönnen auch in nicht bewerteten Kontexten nur mit unqualifiziertem Namen aufgerufen werden, wenn sie verwendet werden können ([over.call.func] p3).
  • Ein Verweis auf eine nicht statische Elementfunktion nach qualifiziertem Namen oder Elementzugriff erfordert einen Verweis auf den zu definierenden Typ

Ich denke, ich kann abschließend sagen, dass es überhaupt keine Möglichkeit gibt self, etwas zu implementieren, ohne irgendwo den Typnamen anzugeben.

Bearbeiten : Es gibt einen Fehler in meiner früheren Argumentation. "Nicht statische Elementfunktionen können auch in nicht bewerteten Kontexten nur mit unqualifiziertem Namen aufgerufen werden, wenn dies verwendet werden kann ([over.call.func] p3)", ist falsch. Was es tatsächlich sagt, ist

Wenn sich das Schlüsselwort this(9.3.2) im Gültigkeitsbereich befindet und auf eine Klasse Toder eine abgeleitete Klasse von verweist, Tlautet das implizite Objektargument (*this). Wenn sich das Schlüsselwort thisnicht im Gültigkeitsbereich befindet oder auf eine andere Klasse verweist, wird ein erfundenes Objekt vom Typ Tzum implizierten Objektargument. Wenn die Argumentliste durch ein erfundenes Objekt erweitert wird und die Überlastungsauflösung eine der nicht statischen Elementfunktionen von auswählt T, ist der Aufruf fehlerhaft.

Innerhalb einer statischen Elementfunktion wird sie thismöglicherweise nicht angezeigt, ist jedoch weiterhin vorhanden.

Gemäß den Kommentaren wird jedoch innerhalb einer statischen Elementfunktion die Transformation von f()to (*this).f()nicht ausgeführt, und wenn diese nicht ausgeführt wird, wird [expr.call] p1 verletzt:

[...] Bei einem Mitgliedsfunktionsaufruf muss der Postfix-Ausdruck ein impliziter (9.3.1, 9.4) oder expliziter Klassenmitgliedszugriff (5.2.5) sein, dessen [...]

da es keinen Mitgliederzugang geben würde. Selbst das würde nicht funktionieren.


quelle
Ich denke, [class.mfct.non-static] / 3 sagt, dass dies _self_fn_1()"transformiert" ist (*this)._self_fn_1(). Ich bin mir nicht sicher, ob das illegal ist.
Dyp
@dyp Es heißt "wird in einem Mitglied der Klasse Xin einem Kontext verwendet, in dem es thisverwendet werden kann", daher glaube ich nicht, dass eine Transformation durchgeführt wird.
1
Aber dann ist es weder ein impliziter noch ein expliziter Zugriff auf Klassenmitglieder. [expr.call] / 1 "Für einen Mitgliedsfunktionsaufruf muss der Postfix-Ausdruck ein impliziter oder expliziter [...] Zugriff auf
Klassenmitglieder sein
(Ich meine, was passiert, wenn Sie haben auto _self_fn_1() -> decltype(*this); auto _self_fn_1() const -> decltype(*this);?)
Dyp
@dyp [expr.call] / 1 ist ein guter Punkt, ich muss genauer hinsehen. Über constÜberlastungen: Das ist kein Problem. Speziell modifiziert worden , um auch auf statische Elementfunktionen anwenden, und sagt die Art der 5.1p3 hat thisist Foo*/ Bar*(ohne const), weil es keine constin der Erklärung _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
};

Dies funktioniert nicht bei Vorlagentypen, wie sie self_checknicht aufgerufen werden, daher static_assertwird die nicht ausgewertet.

Wir können einige Hacks durchführen, damit es auch für templates funktioniert , aber es hat nur geringe Laufzeitkosten.

#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

structIn Ihrer Klasse wird ein leeres Byte der Größe 1 erstellt. Wenn Ihr Typ instanziiert ist, selfwird gegen getestet.

Yakk - Adam Nevraumont
quelle
Das ist auch nicht schlecht!
Leichtigkeitsrennen im Orbit
@LightnessRacesinOrbit jetzt mit templateOptionen zur Klassenunterstützung.
Yakk - Adam Nevraumont
Ich habe genau darüber nachgedacht, als ich gestern die Arbeit verließ. Du warst schneller als ich :). Ich würde vorschlagen, self_check () als Inline zu deklarieren, um Verknüpfungsprobleme zu vermeiden (dasselbe Symbol Foo :: self_check () in mehreren Objektdateien).
das Schwein
1
@theswine: 9.3 / 2 ist der Index eines Absatzes im C ++ - Standard, der garantiert, dass die im Hauptteil der Klassendefinition definierten Klassenelementfunktionen bereits implizit vorhanden sind inline. Das heißt, Sie müssen überhaupt nicht schreiben inline. Wenn Sie also inlinewährend Ihrer gesamten Karriere vor jeder solchen Funktionsdefinition für Klassenmitglieder geschrieben haben, können Sie jetzt aufhören;)
Leichtigkeitsrennen im Orbit
2
@ LightnessRacesinOrbit Oh, eigentlich war ich. Vielen Dank, das erspart mir in Zukunft das Tippen :). Ich bin immer wieder erstaunt, wie viel ich über C ++ nicht weiß.
das Schwein
11

Ich denke auch, dass es unmöglich ist, hier ist ein weiterer fehlgeschlagener, aber meiner Meinung nach interessanter Versuch, der den thisZugriff vermeidet :

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, "" );
}

Dies schlägt fehl, weil Sie sich in C ++ für self_fdie Klasse qualifizieren müssen, wenn Sie die Adresse übernehmen möchten :(

Daniel Frey
quelle
Das gleiche Problem tritt bei einem regulären int T::*Zeiger auf die Mitgliedsvariable auf. Und int self_var; typedef decltype(&self_var) self_ptrfunktioniert auch nicht, das ist nur ein normaler int*.
MSalters
9

Ich habe kürzlich entdeckt, *thisdass dies in einem Brace-or-Equal-Initialisierer zulässig ist . Beschrieben in § 5.1.1 ( aus dem Arbeitsentwurf n3337 ):

3 [..] Im Gegensatz zum Objektausdruck in anderen Kontexten *thismuss er für den Zugriff auf Klassenmitglieder (5.2.5) außerhalb des Elementfunktionskörpers nicht vollständig sein. [..]

4 Wenn ein Member-Deklarator ein nicht statisches Datenelement (9.2) einer Klasse X deklariert, ist der Ausdruck thisein Wert vom Typ „Zeiger auf X“ innerhalb des optionalen Klammer-oder-Gleich-Initialisierers . Es darf nicht an anderer Stelle im Mitgliederdeklarator erscheinen .

5 Der Ausdruck thisdarf in keinem anderen Zusammenhang erscheinen. [ Beispiel:

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
        };
    }
};

- Beispiel beenden ]

In diesem Sinne den folgenden Code:

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, "" );
}

geht an Daniel Freys vorbei static_assert.

Live example

Gemeinschaft
quelle
Sie haben eine lästige nutzlos Variable testobwohl
MM
@ Matt Stimmt, aber ich fand es trotzdem interessant.
1
Das hätte auch funktionieren können = this, oder? Und warum nicht einfachusing self = Foo*;
user362515
1
Wir haben hier sicherlich nichts gewonnen, weil wir erklären mussten, dass wir testvom Typ sind, ähm Foo *!
Paul Sanders
4

Sofern der Typ nicht Mitgliedstyp der einschließenden Klasse sein muss, können Sie die Verwendung von selfdurch ersetzen decltype(*this). Wenn Sie es an vielen Stellen in Ihrem Code verwenden, können Sie ein Makro SELFwie folgt definieren:

#define SELF decltype(*this)
TAS
quelle
2
Und Sie können das nicht außerhalb der Klasse oder in verschachtelten Klassen verwenden
Drax
1
@Drax: Es soll nicht außerhalb der Klasse verfügbar sein.
Ben Voigt
@ BenVoigt Aber es wird angenommen, dass es in verschachtelten Klassen verfügbar ist, was IMO der interessanteste Anwendungsfall ist.
Drax
1
Das glaube ich nicht. Sollte sich nicht selfauf die unmittelbar einschließende Klasse und nicht auf die äußere Klasse beziehen? Aber ich kenne PHP nicht so gut.
Ben Voigt
1
@LightnessRacesinOrbit: Ich denke, dass Code und Fehler sagen sollen "PHP hat keine verschachtelten Typen"?
Ben Voigt
1

Geben Sie meine Version an. Das Beste ist, dass seine Verwendung der der einheimischen Klasse entspricht. Es funktioniert jedoch nicht für Vorlagenklassen.

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); }
};
user1899020
quelle
1

Aufbauend auf der Antwort von hvd stellte ich fest, dass das einzige, was fehlte, das Entfernen der Referenz war. Deshalb schlägt die Prüfung std :: is_same fehl (b / c ist der resultierende Typ tatsächlich eine Referenz auf den Typ). Jetzt kann dieses parameterlose Makro die ganze Arbeit erledigen. Arbeitsbeispiel unten (ich benutze 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";
}
niksbenik
quelle
Es wird nicht auf anderen Compilern als GCC kompiliert.
Zedu
0

Ich werde die offensichtliche Lösung wiederholen, "es selbst tun zu müssen". Dies ist die prägnante C ++ 11-Version des Codes, die sowohl mit einfachen Klassen als auch mit Klassenvorlagen funktioniert:

#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")

Sie können es bei ideone in Aktion sehen . Die Genese, die zu diesem Ergebnis führt, ist unten:

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

struct XYZ {
    DECLARE_SELF(XYZ)
};

Dies hat das offensichtliche Problem, den Code in eine andere Klasse einzufügen und zu vergessen, XYZ zu ändern, wie hier:

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

Mein erster Ansatz war nicht sehr originell - eine Funktion wie diese zu erstellen:

/**
 *  @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

Es ist etwas langwierig, aber bitte nehmen Sie mich hier mit. Dies hat den Vorteil, in C ++ 03 ohne zu arbeiten decltype, da die __self_check_helperFunktion verwendet wird, um den Typ von abzuleiten this. Auch gibt es keine static_assert, aber der sizeof()Trick wird stattdessen angewendet. Sie könnten es für C ++ 0x viel kürzer machen. Dies funktioniert jetzt nicht mehr für Vorlagen. Es gibt auch ein kleines Problem mit dem Makro, das am Ende kein Semikolon erwartet. Wenn es mit pedantisch kompiliert wird, wird es sich über ein zusätzliches unnötiges Semikolon beschweren (oder Sie werden mit einem seltsam aussehenden Makro zurückbleiben, das nicht mit einem Semikolon im Hauptteil von XYZund endet ABC).

Eine Überprüfung der Typeübergebenen Daten DECLARE_SELFist keine Option, da dadurch nur die XYZKlasse überprüft wird (was in Ordnung ist), ohne zu wissen ABC(was fehlerhaft ist). Und dann traf es mich. Eine kostengünstige Lösung ohne zusätzlichen Speicher, die mit Vorlagen funktioniert:

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 

Dadurch wird lediglich eine statische Zusicherung für einen eindeutigen Aufzählungswert vorgenommen (oder zumindest für den Fall, dass Sie nicht den gesamten Code in eine einzelne Zeile schreiben), es wird kein Trick zum Vergleichen von Typen angewendet, und es funktioniert auch in Vorlagen als statische Zusicherung . Und als Bonus - das letzte Semikolon ist jetzt erforderlich :).

Ich möchte Yakk dafür danken, dass er mich gut inspiriert hat. Ich würde das nicht schreiben, ohne zuerst seine Antwort zu sehen.

Getestet mit VS 2008 und g ++ 4.6.3. In der Tat beschwert es sich mit dem XYZund ABCBeispiel:

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

Wenn wir nun ABC zu einer Vorlage machen:

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

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

Wir werden bekommen:

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>â

Nur die Zeilennummernprüfung wurde ausgelöst, da die Funktionsprüfung nicht (wie erwartet) kompiliert wurde.

Mit C ++ 0x (und ohne die bösen Unterstriche) benötigen Sie nur:

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

Ich glaube, dass das CStaticAssert-Bit leider immer noch benötigt wird, da es einen Typ erzeugt, der im Vorlagenkörper typisiert ist (ich nehme an, dass dies nicht möglich ist static_assert). Der Vorteil dieses Ansatzes sind immer noch die Nullkosten.

das Schwein
quelle
Sie implementieren static_asserthier im Wesentlichen neu , nicht wahr? Darüber hinaus ist Ihr vollständiger Code ungültig, da Sie illegale (reservierte) Bezeichner verwenden.
Konrad Rudolph
@KonradRudolph Ja, das ist in der Tat der Fall. Ich habe zurzeit kein C ++ 0x, daher habe ich static_assert neu implementiert, um eine vollständige Antwort zu erhalten. Das sage ich in der Antwort. Ist es ungültig? Können Sie darauf hinweisen, wie? Es wurde gut kompiliert, ich benutze es gerade.
das Schwein
1
Die Bezeichner sind ungültig, da C ++ alles mit einem führenden Unterstrich gefolgt von einem Großbuchstaben sowie zwei führenden Unterstrichen im globalen Bereich für den Compiler reserviert. Benutzercode darf ihn nicht verwenden, aber nicht alle Compiler kennzeichnen ihn als Fehler.
Konrad Rudolph
@KonradRudolph Ich verstehe, das wusste ich nicht. Ich habe eine Menge Code, der das verwendet, hatte nie Probleme damit unter Linux / Mac / Windows. Aber ich denke es ist gut zu wissen.
das Schwein
0

Ich weiß nicht alles über diese verrückten Vorlagen, wie wäre es mit etwas Supereinfachem:

#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

Arbeit erledigt, es sei denn, Sie können ein paar Makros nicht ausstehen. Sie können sogar verwendenCLASSNAME Ihre Konstruktoren (und natürlich den Destruktor) deklarieren.

Live-Demo .

Paul Sanders
quelle
1
Es hat einen ziemlich ausgeprägten Einfluss darauf, wie die Klasse dann verwendet werden kann / muss
Lightness Races in Orbit
@LightnessRacesinOrbit Wie so? Ich sehe es nicht Ich habe nachdenklich den letzten Satz meines ursprünglichen Beitrags entfernt. Was ich ursprünglich dort hatte, könnte Sie dazu gebracht haben, dies zu denken.
Paul Sanders