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.)
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.
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.
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) {
};
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.
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),this
peut être utilisé ([over.call.func] p3),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 classeT
ou à une classe dérivée deT
, 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 typeT
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 deT
, 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.
#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 template
s 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é.
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 :(
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
this
est 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
this
ne 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
.
À 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)
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";
}
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); }
};
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).
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.