111 votes

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

C++ manque l'équivalent de PHP self mot-clé qui est évalué au type de la classe englobante.

C'est assez facile de le simuler sur une base par classe :

struct Foo
{
   typedef Foo self;
};

mais je devais écrire Foo encore. Peut-être qu'un jour je me tromperai et causerai un bug silencieux.

Puis-je utiliser une combinaison de decltype et amis pour que cela fonctionne de manière "autonome" ? J'ai déjà essayé ce qui suit mais this n'est pas valable à 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).

9 votes

this_t serait probablement plus conforme à la dénomination normale du C++.

3 votes

@BartekBanachewicz : ou ce_type

0 votes

Accepterez-vous aussi les réponses C++14 ? Cela pourrait fonctionner dans ce cas.

41voto

Ralph Tandetzky Points 5310

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 voulez dériver de Foo alors vous devez utiliser la macro WITH_SELF_DERIVED de la manière suivante :

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

Vous pouvez même faire de l'héritage multiple 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 avec gcc 4.8 et clang 3.4.

23 votes

Je suppose que la réponse est "non, mais Ralph le peut !" ;)

3 votes

En quoi cela est-il supérieur au simple fait de mettre le typedef ? Et mon Dieu, pourquoi auriez-vous même besoin du typedef ? pourquoi ?

7 votes

@MilesRout C'est une question sur la question, pas sur la réponse. Dans de nombreux cas dans le développement de logiciels (et surtout dans la maintenance), il est utile d'éviter les redondances dans le code, afin que changer quelque chose à un endroit ne nécessite pas de changer du code à un autre endroit. C'est tout l'intérêt de auto et decltype ou dans ce cas de self .

39voto

Paranaix Points 5136

Une solution de contournement possible (puisque 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 T dérive en fait de Self<T> :

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

Notez qu'un static_assert à l'intérieur d'une fonction membre est probablement la seule façon de vérifier, car les types passés à la fonction std::is_base_of doivent être complètes.

4 votes

Pas besoin de typename dans la typologie. Et comme cela ne réduit pas le nombre de redondances, je ne pense pas que ce soit une alternative viable.

0 votes

Il a exactement le même problème de répétition Foo nom.

6 votes

Il est Cependant, elle est légèrement meilleure que l'approche originale, car les répétitions sont très rapprochées. Ce n'est pas une solution à la question, mais +1 pour une tentative louable d'une solution de contournement dans le meilleur des cas.

32voto

Bartek Banachewicz Points 13173

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

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

Et ensuite utiliser comme

CLASS_WITH_SELF(Foo) 
};

#define END_CLASS }; aiderait probablement à la lisibilité.


Vous pourriez aussi prendre le programme de @Paranaix Self et l'utiliser (cela commence à devenir vraiment hackish)

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

class WITH_SELF(Foo) {
};

19 votes

EWWWW END_CLASS. C'est totalement inutile.

31 votes

@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 le } est "suspendu", ce que les éditeurs de texte n'apprécieraient probablement pas non plus.

7 votes

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 le scoping du C++, c'est-à-dire si elle était utilisable en tant que CLASS_WITH_SELF(foo) { … }; - et je pense que c'est impossible à réaliser.

32voto

Konrad Rudolph Points 231505

Je n'ai pas de preuve positive mais je pensez à c'est impossible. Ce qui suit échoue - pour la même raison que votre tentative - et je pense que c'est le plus loin que nous puissions aller :

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

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

Essentiellement, ce que cela démontre, c'est que la portée à laquelle nous voulons déclarer notre typedef a simplement pas de l'accès (qu'il soit direct ou indirect) à this et il n'y a pas d'autre moyen (indépendant du compilateur) d'obtenir le type ou le nom de la classe.

4 votes

Cela sera-t-il possible avec la déduction du type de retour de C++1y ?

0 votes

@dyp : Pas sous cette forme puisque self_() doit être appelé sur un objet.

4 votes

Pour les besoins de ma réponse, cela ne changera rien. L'erreur ici n'est pas dans le type de retour qui suit, elle est dans l'invocation.

17voto

Yakk Points 31636
#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èles, car self_check n'est pas appelé, donc le static_assert n'est pas évalué.

On peut faire quelques piratages pour que ça marche pour template également, mais cela a un coût mineur en termes de temps d'exécution.

#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 vide struct de taille 1 octet est créé dans votre classe. Si votre type est instancié, self est testé.

0 votes

Ce n'est pas mal non plus !

0 votes

@LightnessRacesinOrbit maintenant avec template options de soutien de la classe.

0 votes

Je pensais exactement à ça en quittant le travail hier. Vous m'avez devancé :). Je suggérerais 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).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X