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_helper
fonction 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 XYZ
et ABC
).
Faire une vérification sur le Type
qui est passé DECLARE_SELF
n'est pas une option, car cela ne vérifierait que la XYZ
classe (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 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 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.
this_t
serait probablement plus aligné avec la dénomination C ++ classique.