web-dev-qa-db-fra.com

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

C++ lacks l'équivalent de self mot clé de PHP , qui correspond au type de la classe englobante.

Il est assez facile de le simuler classe par classe:

struct Foo
{
   typedef Foo self;
};

mais je devais écrire Foo encore. Peut-être que je me trompe un jour et cause un bug silencieux.

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

97

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, utilisez la macro WITH_SELF_DERIVED de 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. 

35
Ralph Tandetzky

Une solution de contournement possible (car vous devez encore é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 pouvons nous assurer que T dérive réellement de Self<T>:

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

Notez que static_assert dans une fonction membre est probablement le seul moyen de vérifier, car les types passés àstd::is_base_of doivent être complets.

37
Sebastian Hoffmann

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

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

Et puis utiliser comme 

CLASS_WITH_SELF(Foo) 
};

#define END_CLASS }; aiderait probablement la lisibilité.


Vous pouvez aussi prendre la variable Self de @ Paranaix et l'utiliser (ça commence à devenir vraiment fictif)

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

class WITH_SELF(Foo) {
};
34
Bartek Banachewicz

Je n’ai aucune preuve positive, mais jpense que c’est impossible. Ce qui suit échoue - pour les mêmes raisons que votre tentative - et je pense que c’est le plus loin que nous puissions obtenir:

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

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

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

31
Konrad Rudolph

Ce qui fonctionne à la fois dans GCC et dans clang est de créer un typedef qui se réfère à this en utilisant this dans le type fin-retour d'une fonction typedef. Etant donné qu'il ne s'agit pas d'une déclaration de fonction membre statique, l'utilisation de this est 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 ceci n’est pas valable. Clang vérifie que this n'est pas utilisé dans la définition d'une fonction membre statique. Et ici, ce n'est vraiment pas. Cela ne dérange pas GCC si this est utilisé dans un type fin-retour quel que soit le type de fonction, il le permet même pour les fonctions membres static. Cependant, ce que la norme exige en réalité, c'est que this ne soit pas utilisé 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 de membres non statiques ([expr.prim.general] p5),
  • les types de données membres 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, lorsque this peut être utilisé ([over.call.func] p3),
  • une référence à une fonction membre non statique par nom qualifié ou accès membre nécessite une référence au type en cours de définition

Je pense pouvoir affirmer avec certitude qu'il n'y a aucun moyen d'implémenter self sans inclure, d'une manière ou d'une autre, quelque part, le nom du type.

Edit : Il y a une faille dans mon raisonnement précédent. "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. Ce que réellement dit, c’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 impliqué est (*this). Si le mot clé this n'est pas dans la portée ou fait référence à une autre classe, un objet artificiel de type T devient l'argument de l'objet impliqué. Si la liste d'arguments est complété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é.

Dans une fonction membre statique, this peut ne pas apparaître, mais il existe toujours.

Cependant, selon les commentaires, dans une fonction membre statique, la transformation de f() en (*this).f() ne serait pas effectuée et si ce n'est pas le cas, [expr.call] est violé:

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

car il n'y aurait pas d'accès membre. Donc, même cela ne fonctionnerait pas.

20
user743382
#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, étant donné que self_check n'est pas appelé, donc static_assert n'est pas évalué.

Nous pouvons faire quelques trucs pour que ça marche pour templates aussi, mais cela a un coût minime en terme 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 struct vide de taille 1 est créé dans votre classe. Si votre type est instancié, self est testé.

17

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

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

ce qui échoue car C++ nécessite que vous qualifiiez self_f avec la classe lorsque vous voulez prendre son adresse :(

11
Daniel Frey

J'ai récemment découvert que *this est autorisé dans un accolade-ou-égal-initialiseur . Décrit au § 5.1.1 ( du brouillon de travail n3337):

3 [..] Unlike the object expression in other contexts, *this is not required to be of complete type for purposes of class member access (5.2.5) outside the member function body. [..]

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’initialisateur facultatif accolade-ou-égal . Il ne doit pas apparaître ailleurs dans le membre-déclarateur .

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

passe Daniel Freystatic_assert.

Live example

8
user3920237

À moins que le type doive être membre du type de la classe englobante, vous pouvez remplacer l'utilisation de self par decltype(*this). Si vous l'utilisez à plusieurs endroits de votre code, vous pouvez définir une macro SELF comme suit:

#define SELF decltype(*this)
4
TAS

En me basant sur la réponse de hvd, j’ai trouvé que la seule chose qui manquait était de supprimer la référence, c’est pourquoi la vérification std :: is_same échoue (car le type obtenu est en réalité 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";
}
2
niksbenik

Fournir ma version. La meilleure chose à faire est que son utilisation est la même que celle de 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); }
};
1
user1899020

Je ne connais pas tous ces modèles loufoques, pourquoi pas quelque chose de très 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 accompli, à moins que vous ne supportiez pas quelques macros. Vous pouvez même utiliser CLASSNAME pour déclarer votre ou vos constructeurs (et, bien sûr, votre destructeur).

Démo en direct .

0
Paul Sanders

Je vais répéter la solution évidente de "devoir le faire soi-même". Voici la version succincte C++ 11 du code, qui fonctionne à la fois avec des classes simples et des modèles de 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")

Vous pouvez le voir en action sur ideone . La genèse menant à ce résultat est la suivante:

#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 s'il vous plaît, supportez-moi ici. Cela présente l’avantage de fonctionner en C++ 03 sans decltype, car la fonction __self_check_helper est utilisée pour déduire le type de this. En outre, il n'y a pas static_assert, mais l'astuce sizeof() est utilisée à la place. Vous pourriez 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 le point-virgule à la fin. Si vous compilez avec pedantic, il se plaindra d'un point-virgule super inutile (ou vous laisserez une macro étrange ne se terminant pas par un point-virgule dans le corps de XYZ et ABC).

Vérifier la valeur Type transmise à DECLARE_SELF n'est pas une option, car cela ne ferait que vérifier la classe XYZ (ce qui est correct), sans se rendre compte de ABC (qui comporte une erreur). Et ensuite ça m'a frappé. Une solution sans coût supplémentaire de 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 l'assertion statique sur une valeur enum unique (ou du moins si vous n'écrivez pas tout votre code sur une seule ligne), aucune astuce de comparaison de type n'est utilisée et fonctionne comme une assertion statique, même dans les modèles. . Et en prime - le dernier point-virgule est maintenant requis :).

J'aimerais remercier Yakk de m'avoir donné une bonne inspiration. Je n'écrirais pas ceci sans voir d'abord sa réponse.

Testé avec VS 2008 et g ++ 4.6.3. En effet, avec les exemples XYZ et 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 de l’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 obtiendrons:

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 soulignements diaboliques), il vous suffirait 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 pense que le bit CStaticAssert est malheureusement toujours nécessaire car il produit un type, qui est typé-ed dans le corps du modèle (je suppose que cela ne peut pas être fait avec static_assert). L'avantage de cette approche reste son coût nul.

0
the swine