Puis-je implémenter un type de membre autonome `self` en C ++?

101

C ++ n'a pas l'équivalent du mot - selfclé PHP , qui évalue le type de la classe englobante.

Il est assez facile de le simuler par classe:

struct Foo
{
   typedef Foo self;
};

mais je devais écrire Foo nouveau. Peut-être que je vais me tromper un jour et provoquer un bug silencieux.

Puis-je utiliser une combinaison de decltypeet d'amis pour faire ce travail "de manière autonome"? J'ai déjà essayé ce qui suit mais ce thisn'est pas valide à cet endroit:

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

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

(Je ne vais pas m'inquiéter de l'équivalent de static, qui fait la même chose mais avec une liaison tardive.)

Courses de légèreté en orbite
la source
9
this_tserait probablement plus aligné avec la dénomination C ++ classique.
Bartek Banachewicz
3
@BartekBanachewicz: or this_type
PlasmaHH
10
@Praetorian, je ne me souviens pas si c'était une proposition ou non, mais quelqu'un a suggéré auto()et ~auto()pour les ctors / dtors. Intéressant pour dire le moins. Si utilisé à cette fin, peut-être typedef auto self;, mais cela me semble un peu sommaire.
chris
11
Honnêtement, si j'allais suggérer une syntaxe pour rendre cela possible, ce serait probablement decltype(class), peut-être avec un decltype(struct)équivalent. C'est beaucoup plus clair que autodans un contexte spécifique et je ne vois aucun problème avec son intégration dans le langage basé sur decltype(auto).
chris
11
Puisque vous voulez éviter les erreurs, vous pouvez configurer une fonction membre factice avec static_assert, comme void _check() { static_assert(std::is_same<self&, decltype(*this)>::value, "Correct your self type"); }Ne fonctionne pas avec les modèles de classe, bien que ...
milleniumbug

Réponses:

39

Voici comment vous pouvez le faire sans répéter le type de 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;
    }
};

Si vous souhaitez dériver de, Foovous devez utiliser la macro WITH_SELF_DERIVEDde la manière suivante:

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

Vous pouvez même faire plusieurs héritages avec autant de classes de base que vous le souhaitez (grâce aux modèles variadiques et aux macros variadiques):

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

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

J'ai vérifié que cela fonctionne sur gcc 4.8 et clang 3.4.

Ralph Tandetzky
la source
18
Je suppose que la réponse est "non, mais Ralph peut!" ;)
Courses de légèreté en orbite
3
En quoi est-ce supérieur de quelque manière que ce soit à simplement y mettre le typedef? Et mon Dieu, pourquoi auriez-vous même besoin du typedef? Pourquoi?
Miles Rout
7
@MilesRout C'est une question sur la question, pas sur la réponse. Dans de nombreux cas dans le développement de logiciels (et en particulier la maintenance), il est utile d'éviter les redondances dans le code, de sorte que changer quelque chose à un endroit ne vous oblige pas à changer de code à un autre endroit. C'est tout l'intérêt de autoet decltypeou dans ce cas de self.
Ralph Tandetzky
1
template<typename T>class Self{protected: typedef T self;}; class WITH_SELF(Foo) : public Bar, private Baz {};aurait été plus simple et permettrait un contrôle plus précis de l'héritage - des raisons contre?
Aconcagua
@mmmmmmmm, si vous n'avez pas appris à apprécier profondément le principe "Ne vous répétez pas", il y a de fortes chances que vous n'ayez pas encore suffisamment codé / sérieusement codé. Ce «fouillis» (loin de là, en fait) est une solution assez élégante dans le contexte de parler d'une fonctionnalité de langage inélégante (ou d'un dysfonctionnement, voire d'une carence par certaines mesures strictes).
Sz.
38

Une solution de contournement possible (car vous devez toujours écrire le type une fois):

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

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

Pour une version plus sûre, nous pourrions nous assurer que Tdérive réellement de Self<T>:

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

Notez qu'une static_assertfonction à l'intérieur d'une fonction membre est probablement le seul moyen de vérifier, car les types passés std::is_base_ofdoivent être complets.

Sebastian Hoffmann
la source
4
Pas besoin de typenamedans le typedef. Et comme cela ne réduit pas le nombre de licenciements, je ne pense pas que ce soit une alternative viable.
Konrad Rudolph
Il a exactement le même problème de répétition de Foonom.
Bartek Banachewicz
6
Elle est cependant légèrement meilleure que l'approche originale, car la répétition est très rapprochée. Pas une solution à la question, mais +1 pour une tentative digne d'une solution de contournement dans le meilleur des cas.
Courses de légèreté en orbite le
4
J'ai utilisé cette solution plusieurs fois, et elle a une mauvaise chose: lorsque vous dérivez plus tard de Foo, vous devez soit: (1) propager le T vers le haut au descendant de la feuille, ou (2) vous souvenir d'hériter de SelfT plusieurs fois , ou (3) accepter que tous les enfants soient la base .. utilisable, mais peu jolie.
quetzalcoatl
@quetzalcoatl: Puisque j'essaye de répliquer selfplutôt que static, ce n'est pas un problème.
Courses de légèreté en orbite le
33

Vous pouvez utiliser une macro au lieu d'une déclaration de classe ordinaire, qui le fera pour vous.

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

Et puis utilisez comme

CLASS_WITH_SELF(Foo) 
};

#define END_CLASS }; aiderait probablement la lisibilité.


Vous pouvez également prendre @ Paranaix's Selfet l'utiliser (cela commence à devenir vraiment hackish)

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

class WITH_SELF(Foo) {
};
Bartek Banachewicz
la source
18
EWWWW END_CLASS. C'est totalement inutile.
Puppy
31
@DeadMG Je pense que certaines personnes aimeraient plus de cohérence; après tout, la première utilisation de la macro ne se termine pas par {, donc }c'est "suspendu", ce que les éditeurs de texte n'aimeraient probablement pas aussi.
Bartek Banachewicz
6
Bonne idée mais même si je ne suis pas fondamentalement opposé aux macros, je n'accepterais son utilisation ici que si elle imitait la portée C ++, c'est-à-dire si elle était utilisable comme CLASS_WITH_SELF(foo) { … };- et je pense que c'est impossible à réaliser.
Konrad Rudolph
2
@KonradRudolph J'ai également ajouté un moyen de le faire. Pas que je l'aime, juste pour être complet
Bartek Banachewicz
1
Cependant, cette approche pose certains problèmes. Le premier ne vous permet pas de faire hériter facilement la classe (à moins que vous n'utilisiez un ou plusieurs paramètres de macro), et le second a tous les problèmes d' hériter de celui-ci Self.
Bartek Banachewicz
31

Je n'ai aucune preuve positive mais je pense que c'est impossible. Ce qui suit échoue - pour la même raison que votre tentative - et je pense que c'est le plus loin possible:

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

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

Essentiellement, cela démontre que la portée à laquelle nous voulons déclarer notre typedef n'a tout simplement aucun accès (direct ou indirect) à this, et il n'y a pas d'autre moyen (indépendant du compilateur) d'accéder au type ou au nom de la classe.

Konrad Rudolph
la source
4
Sera-ce possible avec la déduction du type de retour de C ++ 1y?
dyp
4
@dyp Pour les besoins de ma réponse, cela ne changera rien. L'erreur ici n'est pas dans le type de retour de fin, c'est dans l'invocation.
Konrad Rudolph
1
@quetzalcoatl: Les entrailles de decltypesont un contexte non évalué, donc invoquer la fonction membre n'est pas le problème (cela ne sera pas tenté)
Courses de légèreté en orbite
1
@TomKnapen Essayez-le avec clang, et cela échouera. Le fait qu'il soit accepté par GCC est un bogue, pour autant que je sache.
4
FWIW, struct S { int i; typedef decltype(i) Int; };fonctionne même s'il is'agit d'un membre de données non statique. Cela fonctionne car il y decltypea une exception spéciale où un nom simple n'est pas évalué comme une expression. Mais je ne vois aucun moyen d'utiliser cette possibilité d'une manière qui réponde à la question.
21

Ce qui fonctionne à la fois dans GCC et clang est de créer un typedef qui se réfère à thisen utilisant thisdans le type de retour de fin d'une fonction typedef. Comme il ne s'agit pas de la déclaration d'une fonction membre statique, l'utilisation de thisest tolérée. Vous pouvez ensuite utiliser ce typedef pour définir self.

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

struct Foo {
    DEFINE_SELF();
};

struct Bar {
    DEFINE_SELF();
};

Malheureusement, une lecture stricte de la norme indique que même cela n'est pas valable. Ce que fait clang est de vérifier que ce thisn'est pas utilisé dans la définition d'une fonction membre statique. Et ici, ce n'est en effet pas. GCC ne se soucie pas de savoir s'il thisest utilisé dans un type de retour de fin quel que soit le type de fonction, il le permet même pour staticles fonctions membres. Cependant, ce que la norme exige réellement, c'est quethis n'est pas utilisée en dehors de la définition d'une fonction membre non statique (ou d'un initialiseur de membre de données non statique). Intel a raison et rejette cela.

Étant donné que:

  • this n'est autorisé que dans les initialiseurs de membres de données non statiques et les fonctions membres non statiques ([expr.prim.general] p5),
  • Les membres de données non statiques ne peuvent pas avoir leur type déduit de l'initialiseur ([dcl.spec.auto] p5),
  • Les fonctions membres non statiques ne peuvent être désignées que par un nom non qualifié dans le contexte d'un appel de fonction ([expr.ref] p4)
  • les fonctions membres non statiques ne peuvent être appelées que par un nom non qualifié, même dans des contextes non évalués, lorsqu'elles thispeuvent être utilisées ([over.call.func] p3),
  • une référence à une fonction membre non statique par un nom qualifié ou un accès membre nécessite une référence au type en cours de définition

Je pense que je peux dire de manière concluante qu'il n'y a aucun moyen de mettre en œuvre self sans inclure d'une manière ou d'une autre, quelque part, le nom du type.

Edit : Il y a un défaut dans mon raisonnement antérieur. "Les fonctions membres non statiques ne peuvent être appelées que par un nom non qualifié, même dans des contextes non évalués, lorsque cela peut être utilisé ([over.call.func] p3)", est incorrect. Qu'est-ce que c'est réellement dit en est

Si le mot-clé this(9.3.2) est dans la portée et fait référence à la classe T, ou à une classe dérivée de T, alors l'argument d'objet implicite est (*this). Si le mot clé thisn'est pas dans la portée ou fait référence à une autre classe, alors un objet artificiel de type Tdevient l'argument d'objet implicite. Si la liste d'arguments est augmentée par un objet artificiel et que la résolution de surcharge sélectionne l'une des fonctions membres non statiques de T, l'appel est mal formé.

À l'intérieur d'une fonction membre statique, this peut ne pas apparaître, mais elle existe toujours.

Cependant, d'après les commentaires, à l'intérieur d'une fonction membre statique, la transformation de f()en(*this).f() ne serait pas effectuée, et si elle n'est pas effectuée, alors [expr.call] p1 est violé:

[...] Pour un appel de fonction membre, l'expression de suffixe doit être un accès de membre de classe implicite (9.3.1, 9.4) ou explicite (5.2.5) dont [...]

car il n'y aurait pas d'accès aux membres. Donc même ça ne marcherait pas.


la source
Je pense que [class.mfct.non-static] / 3 dit que _self_fn_1()c'est "transformé" en (*this)._self_fn_1(). Je ne sais pas si cela le rend illégal, cependant.
dyp
@dyp Il dit "est utilisé dans un membre de classe Xdans un contexte où thispeut être utilisé", donc je ne pense pas que la transformation soit effectuée.
1
Mais alors ce n'est ni un accès de membre de classe implicite ni explicite ..? [expr.call] / 1 "Pour un appel de fonction membre, l'expression de suffixe doit être un accès de membre de classe implicite ou explicite [...]"
dyp
(Je veux dire, que se passe-t-il quand vous avez auto _self_fn_1() -> decltype(*this); auto _self_fn_1() const -> decltype(*this);?)
dyp
@dyp [expr.call] / 1 est un bon point, je vais devoir y regarder de plus près. À propos des constsurcharges, cependant: ce n'est pas un problème. 5.1p3 a été spécifiquement modifié pour s'appliquer également aux fonctions membres statiques, et dit que le type de thisest Foo*/ Bar*(sans const), car il n'y a pas constdans la déclaration de _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
};

cela ne fonctionne pas sur les types de modèle, comme self_checkon ne l'appelle pas, donc le static_assertn'est pas évalué.

Nous pouvons également faire des hacks pour que cela fonctionne pour templates, mais cela a un coût d'exécution mineur.

#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

un structoctet vide de taille 1 est créé dans votre classe. Si votre type est instancié, selfest testé.

Yakk - Adam Nevraumont
la source
Ce n'est pas mal non plus!
Courses de légèreté en orbite le
@LightnessRacesinOrbit maintenant avec templatedes options de support de classe.
Yakk - Adam Nevraumont
J'y pensais exactement en quittant mon travail hier. Vous m'avez battu :). Je suggère de déclarer self_check () comme inline, pour éviter les problèmes de liaison (même symbole Foo :: self_check () trouvé dans plusieurs fichiers objets).
le porc le
1
@theswine: 9.3 / 2 est l'indice d'un paragraphe dans la norme C ++, qui garantit que les fonctions membres de classe définies dans le corps de la définition de classe sont déjà, implicitement, inline. Cela signifie que vous n'avez pas du tout besoin d'écrire inline. Donc, si vous avez écrit inlinedevant chacune de ces définitions de fonction membre de classe pour toute votre carrière, vous pouvez vous arrêter maintenant;)
Lightness Races in Orbit
2
@LightnessRacesinOrbit Oh, en fait je l'étais. Merci, cela me fera économiser du temps à taper à l'avenir :). Je suis toujours étonné de tout ce que je ne sais pas sur C ++.
le porc le
11

Je pense aussi que c'est impossible, voici une autre tentative intéressante mais échouée à thismon humble avis qui évite l' accès:

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

qui échoue car C ++ vous oblige à vous qualifier self_favec la classe lorsque vous voulez prendre son adresse :(

Daniel Frey
la source
Et le même problème se produit avec un int T::*pointeur régulier vers une variable membre. Et int self_var; typedef decltype(&self_var) self_ptrça ne marche pas non plus, c'est juste un habitué int*.
MSalters
9

J'ai récemment découvert que cela *thisest autorisé dans un initialiseur d'accolade ou d'égalité . Décrit au § 5.1.1 (à partir du projet de travail n3337 ):

3 [..] Contrairement à l'expression d'objet dans d'autres contextes, il *thisn'est pas nécessaire d'être de type complet pour les besoins de l'accès aux membres de classe (5.2.5) en dehors du corps de la fonction membre. [..]

4 Sinon, si un membre-déclarateur déclare un membre de données non statique (9.2) d'une classe X, l'expression thisest une prvalue de type «pointeur vers X» dans l' initialiseur optionnel accolades ou égales . Il ne doit pas figurer ailleurs dans le membre-déclarant .

5 L'expression thisne doit apparaître dans aucun autre contexte. [ Exemple:

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

- fin d'exemple ]

Dans cet esprit, le code suivant:

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

dépasse Daniel Frey static_assert .

Live example

Communauté
la source
Vous avez une variable inutile ennuyeux testsi
MM
@Matt True, mais je l'ai toujours trouvé intéressant.
1
Cela aurait pu fonctionner sans = this, non? Et pourquoi pas justeusing self = Foo*;
user362515
1
Nous ne sommes pas gagner quoi que ce soit ici sûrement, parce que nous devions déclarer testêtre de type, euh, Foo *!
Paul Sanders
4

À moins que le type ne doive être le type de membre de la classe englobante, vous pouvez remplacer l'utilisation de selfpar decltype(*this). Si vous l'utilisez à de nombreux endroits dans votre code, vous pouvez définir une macro SELFcomme suit:

#define SELF decltype(*this)
TAS
la source
2
Et vous ne pouvez pas l'utiliser en dehors de la classe, ou dans des classes imbriquées
Drax
1
@Drax: Il n'est pas censé être disponible en dehors de la classe.
Ben Voigt
@BenVoigt Mais il est supposé être disponible dans les classes imbriquées, ce qui est le cas d'utilisation le plus intéressant de l'OMI.
Drax
1
Je ne pense pas. Ne devrait-il pas selffaire référence à la classe immédiatement englobante et non à la classe externe? Mais je ne connais pas bien php.
Ben Voigt
1
@LightnessRacesinOrbit: Je suppose que le code et l'erreur sont censés dire "PHP n'a pas de types imbriqués"?
Ben Voigt
1

Fournissez ma version. La meilleure chose est que son utilisation est la même que la classe native. Cependant, cela ne fonctionne pas pour les classes de modèles.

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
la source
1

En me basant sur la réponse de hvd, j'ai trouvé que la seule chose qui manquait était la suppression de la référence, c'est pourquoi la vérification std :: is_same échoue (b / c le type résultant est en fait une référence au type). Maintenant, cette macro sans paramètre peut faire tout le travail. Exemple de travail ci-dessous (j'utilise 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
la source
Il ne compile pas sur d'autres compilateurs que GCC.
zedu
0

Je vais répéter la solution évidente de "devoir le faire vous-même". Il s'agit de la version succincte du code C ++ 11, qui fonctionne à la fois avec des classes simples et des modèles de classes:

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

Vous pouvez le voir en action chez ideone . La genèse menant à ce résultat est ci-dessous:

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

struct XYZ {
    DECLARE_SELF(XYZ)
};

Cela pose le problème évident de copier-coller le code dans une classe différente et d'oublier de changer XYZ, comme ici:

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

Ma première approche n'était pas très originale - créer une fonction, comme celle-ci:

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

C'est un peu long, mais veuillez rester avec moi ici. Cela présente l'avantage de travailler en C ++ 03 sans decltype, car la __self_check_helperfonction est utilisée pour déduire le type de this. En outre, il n'y a pas static_assert, mais l' sizeof()astuce est utilisée à la place. Vous pouvez le rendre beaucoup plus court pour C ++ 0x. Maintenant, cela ne fonctionnera pas pour les modèles. De plus, il y a un problème mineur avec la macro qui n'attend pas de point-virgule à la fin, si elle compile avec pedantic, elle se plaindra d'un point-virgule supplémentaire inutile (ou vous vous retrouverez avec une macro étrange ne se terminant pas par un point-virgule dans le corps de XYZet ABC).

Faire une vérification sur le Typequi est passé DECLARE_SELFn'est pas une option, car cela ne vérifierait que la XYZclasse (ce qui est ok), inconscient de ABC(qui a une erreur). Et ensuite ça m'a frappé. Une solution zéro coût de stockage sans stockage supplémentaire qui fonctionne avec des modèles:

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 

Cela rend simplement une assertion statique sur une valeur d'énumération unique (ou au moins unique dans le cas où vous n'écrivez pas tout votre code sur une seule ligne), aucune supercherie de comparaison de type n'est utilisée, et cela fonctionne comme une assertion statique, même dans les modèles . Et en prime - le point-virgule final est maintenant requis :).

Je tiens à remercier Yakk de m'avoir donné une bonne inspiration. Je n'écrirais pas cela sans avoir d'abord vu sa réponse.

Testé avec VS 2008 et g ++ 4.6.3. En effet, avec l' exemple XYZet ABC, il se plaint:

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

Maintenant, si nous faisons d'ABC un modèle:

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

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

Nous allons obtenir:

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

Seule la vérification du numéro de ligne s'est déclenchée, car la vérification de la fonction n'a pas été compilée (comme prévu).

Avec C ++ 0x (et sans les mauvais traits de soulignement), vous auriez juste besoin de:

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

Je crois que le bit CStaticAssert est malheureusement toujours nécessaire car il produit un type, qui est typé dans le corps du modèle (je suppose que la même chose ne peut pas être faite avec static_assert). L'avantage de cette approche est toujours son coût nul.

le porc
la source
Vous réinstallez essentiellement static_assertici, n'est-ce pas? De plus, votre code complet est invalide car vous utilisez des identifiants illégaux (réservés).
Konrad Rudolph
@KonradRudolph Oui, c'est bien le cas. Je n'ai pas C ++ 0x à l'époque, j'ai donc réimplémenté static_assert afin de fournir une réponse complète. Je dis cela dans la réponse. Est-ce invalide? Pouvez-vous indiquer comment? Il s'est bien compilé, je l'utilise en ce moment.
le porc le
1
Les identificateurs ne sont pas valides car C ++ réserve tout avec un trait de soulignement en tête suivi d'une lettre majuscule, ainsi que deux traits de soulignement en tête dans la portée globale, pour le compilateur. Le code utilisateur ne doit pas l'utiliser, mais tous les compilateurs ne le marqueront pas comme une erreur.
Konrad Rudolph
@KonradRudolph Je vois, je ne le savais pas. J'ai beaucoup de code qui l'utilise, je n'ai jamais eu de problèmes avec Linux / Mac / Windows. Mais je suppose que c'est bon à savoir.
le porc le
0

Je ne sais pas tout sur ces modèles farfelus, que diriez-vous de quelque chose de super simple:

#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

Travail terminé, sauf si vous ne supportez pas quelques macros. Vous pouvez même utiliser CLASSNAMEpour déclarer votre (vos) constructeur (s) (et, bien sûr, destructeur).

Démo en direct .

Paul Sanders
la source
1
Cela a un effet assez prononcé sur la façon dont la classe peut / doit ensuite être utilisée
Courses de légèreté en orbite
@LightnessRacesinOrbit Comment ça? Je ne le vois pas. J'ai fait, après réflexion, supprimé la dernière phrase de mon message d'origine. Ce que j'avais là à l'origine vous aurait peut-être amené à penser cela.
Paul Sanders