Ripeterò l'ovvia soluzione di "doverlo fare da solo". Questa è la versione C ++ 11 succinta del codice, che funziona sia con classi semplici che con modelli di 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")
Puoi vederlo in azione su ideone . La genesi che ha portato a questo risultato è di seguito:
#define DECLARE_SELF(Type) typedef Type _TySelf; /**< @brief type of this class */
struct XYZ {
DECLARE_SELF(XYZ)
};
Questo ha l'ovvio problema di copiare e incollare il codice in una classe diversa e dimenticare di modificare XYZ, come qui:
struct ABC {
DECLARE_SELF(XYZ) // !!
};
Il mio primo approccio non è stato molto originale: creare una funzione come questa:
/**
* @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
È un po 'lungo, ma per favore abbi pazienza qui. Questo ha il vantaggio di lavorare in C ++ 03 senza decltype
, poiché la __self_check_helper
funzione viene impiegata per dedurre il tipo di this
. Inoltre, non c'è static_assert
, ma il sizeof()
trucco è invece impiegato. Potresti renderlo molto più breve per C ++ 0x. Ora questo non funzionerà per i modelli. Inoltre, c'è un problema minore con la macro che non si aspetta il punto e virgola alla fine, se si compila con pedante, si lamenterà di un punto e virgola non necessario in più (o rimarrà con una macro dall'aspetto strano che non termina con punto e virgola nel corpo di XYZ
e ABC
).
Fare un controllo su Type
ciò che è passato a DECLARE_SELF
non è un'opzione, in quanto ciò controllerebbe solo la XYZ
classe (che è ok), ignara di ABC
(che ha un errore). E poi mi ha colpito. Una soluzione a costo zero di archiviazione senza aggiunta che funziona con i modelli:
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
Questo rende semplicemente un'asserzione statica su un valore enum univoco (o almeno univoco nel caso in cui non scrivi tutto il codice su una singola riga), non viene impiegato alcun trucco di confronto dei tipi e funziona come asserzione statica, anche nei modelli . E come bonus, ora è richiesto il punto e virgola finale :).
Vorrei ringraziare Yakk per avermi dato una buona ispirazione. Non lo scriverei senza prima vedere la sua risposta.
Testato con VS 2008 e g ++ 4.6.3. Infatti, con l' esempio XYZ
e ABC
, si lamenta:
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>â
Ora, se creiamo ABC un modello:
template <class X>
struct ABC {
DECLARE_SELF(XYZ); // line 92
};
int main(int argc, char **argv)
{
ABC<int> abc;
return 0;
}
Otterremo:
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>â
È stato attivato solo il controllo del numero di riga, poiché il controllo della funzione non è stato compilato (come previsto).
Con C ++ 0x (e senza i malvagi trattini bassi), avresti bisogno solo di:
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
Credo che il bit CStaticAssert sia purtroppo ancora necessario in quanto produce un tipo, che viene digitato nel corpo del modello (suppongo che non si possa fare lo stesso con static_assert
). Il vantaggio di questo approccio è ancora il suo costo zero.
this_t
sarebbe probabilmente più allineato con i normali nomi C ++.