Ich werde die offensichtliche Lösung wiederholen, "es selbst tun zu müssen". Dies ist die prägnante C ++ 11-Version des Codes, die sowohl mit einfachen Klassen als auch mit Klassenvorlagen funktioniert:
#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")
Sie können es bei ideone in Aktion sehen . Die Genese, die zu diesem Ergebnis führt, ist unten:
#define DECLARE_SELF(Type) typedef Type _TySelf; /**< @brief type of this class */
struct XYZ {
DECLARE_SELF(XYZ)
};
Dies hat das offensichtliche Problem, den Code in eine andere Klasse einzufügen und zu vergessen, XYZ zu ändern, wie hier:
struct ABC {
DECLARE_SELF(XYZ) // !!
};
Mein erster Ansatz war nicht sehr originell - eine Funktion wie diese zu erstellen:
/**
* @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
Es ist etwas langwierig, aber bitte nehmen Sie mich hier mit. Dies hat den Vorteil, in C ++ 03 ohne zu arbeiten decltype, da die __self_check_helperFunktion verwendet wird, um den Typ von abzuleiten this. Auch gibt es keine static_assert, aber der sizeof()Trick wird stattdessen angewendet. Sie könnten es für C ++ 0x viel kürzer machen. Dies funktioniert jetzt nicht mehr für Vorlagen. Es gibt auch ein kleines Problem mit dem Makro, das am Ende kein Semikolon erwartet. Wenn es mit pedantisch kompiliert wird, wird es sich über ein zusätzliches unnötiges Semikolon beschweren (oder Sie werden mit einem seltsam aussehenden Makro zurückbleiben, das nicht mit einem Semikolon im Hauptteil von XYZund endet ABC).
Eine Überprüfung der Typeübergebenen Daten DECLARE_SELFist keine Option, da dadurch nur die XYZKlasse überprüft wird (was in Ordnung ist), ohne zu wissen ABC(was fehlerhaft ist). Und dann traf es mich. Eine kostengünstige Lösung ohne zusätzlichen Speicher, die mit Vorlagen funktioniert:
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
Dadurch wird lediglich eine statische Zusicherung für einen eindeutigen Aufzählungswert vorgenommen (oder zumindest für den Fall, dass Sie nicht den gesamten Code in eine einzelne Zeile schreiben), es wird kein Trick zum Vergleichen von Typen angewendet, und es funktioniert auch in Vorlagen als statische Zusicherung . Und als Bonus - das letzte Semikolon ist jetzt erforderlich :).
Ich möchte Yakk dafür danken, dass er mich gut inspiriert hat. Ich würde das nicht schreiben, ohne zuerst seine Antwort zu sehen.
Getestet mit VS 2008 und g ++ 4.6.3. In der Tat beschwert es sich mit dem XYZund ABCBeispiel:
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>â
Wenn wir nun ABC zu einer Vorlage machen:
template <class X>
struct ABC {
DECLARE_SELF(XYZ); // line 92
};
int main(int argc, char **argv)
{
ABC<int> abc;
return 0;
}
Wir werden bekommen:
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>â
Nur die Zeilennummernprüfung wurde ausgelöst, da die Funktionsprüfung nicht (wie erwartet) kompiliert wurde.
Mit C ++ 0x (und ohne die bösen Unterstriche) benötigen Sie nur:
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
Ich glaube, dass das CStaticAssert-Bit leider immer noch benötigt wird, da es einen Typ erzeugt, der im Vorlagenkörper typisiert ist (ich nehme an, dass dies nicht möglich ist static_assert). Der Vorteil dieses Ansatzes sind immer noch die Nullkosten.
this_twäre wahrscheinlich eher auf die reguläre C ++ - Benennung ausgerichtet.