[英]Can I implement an autonomous `self` member type in C++?
C++缺少PHP 的self
關鍵字的等價物,它計算封閉類的類型。
在每個班級的基礎上偽造它很容易:
struct Foo
{
typedef Foo self;
};
但我不得不再次寫Foo
。 也許有一天我會弄錯這個並導致一個無聲的錯誤。
我可以使用decltype
和朋友的一些組合來“自主地”完成這項工作嗎? 我已經嘗試了以下方法, this
在那個地方無效:
struct Foo
{
typedef decltype(*this) self;
};
// main.cpp:3:22: error: invalid use of 'this' at top level
// typedef decltype(*this) self;
(我不會擔心static
的等價物,它的作用相同,但具有后期綁定。)
一種可能的解決方法(因為您仍然必須編寫一次類型):
template<typename T>
struct Self
{
protected:
typedef T self;
};
struct Foo : public Self<Foo>
{
void test()
{
self obj;
}
};
對於更安全的版本,我們可以確保T
實際上派生自Self<T>
:
Self()
{
static_assert(std::is_base_of<Self<T>, T>::value, "Wrong type passed to Self");
}
請注意,成員函數中的static_assert
可能是唯一的檢查方法,因為傳遞給std::is_base_of
的類型必須是完整的。
以下是您可以在不重復 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;
}
};
如果要從Foo
派生,則應按以下方式使用宏WITH_SELF_DERIVED
:
class WITH_SELF_DERIVED(Bar,Foo)
{
/* ... */
};
您甚至可以使用任意數量的基類進行多重繼承(感謝可變參數模板和可變參數宏):
class WITH_SELF(Foo2)
{
/* ... */
};
class WITH_SELF_DERIVED(Bar2,Foo,Foo2)
{
/* ... */
};
我已經驗證了它可以在 gcc 4.8 和 clang 3.4 上工作。
您可以使用宏而不是常規的類聲明,這將為您做到這一點。
#define CLASS_WITH_SELF(X) class X { typedef X self;
然后像這樣使用
CLASS_WITH_SELF(Foo)
};
#define END_CLASS };
可能有助於提高可讀性。
你也可以使用@Paranaix's Self
並使用它(它開始變得非常駭人聽聞)
#define WITH_SELF(X) X : public Self<X>
class WITH_SELF(Foo) {
};
我沒有積極的證據,但我認為這是不可能的。 以下失敗 - 與您的嘗試相同的原因 - 我認為這是我們能得到的最遠距離:
struct Foo {
auto self_() -> decltype(*this) { return *this; }
using self = decltype(self_());
};
本質上,這表明我們想要聲明 typedef 的范圍根本無法訪問(直接或間接) this
,並且沒有其他(獨立於編譯器)獲取類的類型或名稱的方法。
在 GCC 和 clang 中起作用的是通過在函數 typedef 的尾隨返回類型中使用this
創建一個引用this
的 typedef。 由於 this 不是靜態成員函數的聲明,因此允許使用this
。 然后,您可以使用該 typedef 來定義self
。
#define DEFINE_SELF() \
typedef auto _self_fn() -> decltype(*this); \
using self = decltype(((_self_fn*)0)())
struct Foo {
DEFINE_SELF();
};
struct Bar {
DEFINE_SELF();
};
不幸的是,嚴格閱讀該標准表明即使這也是無效的。 clang 所做的是檢查this
是否未在靜態成員函數的定義中使用。 在這里,它確實不是。 GCC 不介意this
是否用於尾隨返回類型,無論函數類型如何,它甚至允許static
成員函數。 但是,該標准實際要求的是, this
不能在非靜態成員函數(或非靜態數據成員初始化器)的定義之外使用。 英特爾做對了並拒絕了這一點。
鑒於:
this
僅在非靜態數據成員初始化器和非靜態成員函數([expr.prim.general]p5)中允許,this
可以使用時([over.call.func]p3),我想我可以肯定地說,如果不以某種方式、某處包含類型名稱,就根本無法實現self
。
編輯:我之前的推理存在缺陷。 “非靜態成員函數只能通過非限定名稱調用,即使在未評估的上下文中,當 this 可以使用時([over.call.func]p3)”是不正確的。 它實際上說的是
如果關鍵字
this
(9.3.2) 在范圍內並引用類T
或T
的派生類,則隱含的對象參數是(*this)
。 如果關鍵字this
不在范圍內或引用另一個類,則T
類型的人為對象將成為隱含的對象參數。 如果參數列表由一個人為的對象擴充,並且重載決議選擇了T
的非靜態成員函數之一,則調用格式錯誤。
在靜態成員函數內部, this
可能不會出現,但它仍然存在。
但是,根據評論,在靜態成員函數內部,將不會執行f()
到(*this).f()
的轉換,並且不會執行它,那么 [expr.call]p1 被違反:
[...] 對於成員函數調用,后綴表達式應是隱式 (9.3.1, 9.4) 或顯式類成員訪問 (5.2.5),其 [...]
因為沒有成員訪問權限。 所以即使這樣也行不通。
#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
};
這不適用於模板類型,因為self_check
沒有被調用,所以static_assert
沒有被評估。
我們可以做一些小技巧讓它也適用於template
,但它的運行時間成本很小。
#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
在您的類中創建一個大小為 1 字節的空struct
。 如果您的類型已實例化,則會針對self
進行測試。
我也認為這是不可能的,這是另一個失敗但恕我直言的有趣嘗試,它避免了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, "" );
}
失敗是因為 C++ 要求您在要獲取其地址時使用該類限定self_f
:(
我最近發現*this
在大括號或相等初始化器中是允許的。 § 5.1.1 中描述(來自n3337 工作草案):
3 [..]與其他上下文中的對象表達式不同,
*this
不需要是完整類型,以便在成員函數體之外訪問類成員(5.2.5)。 [..]4 否則,如果成員聲明符聲明類 X 的非靜態數據成員 (9.2),則表達式
this
是可選大括號或等式初始化器中“指向 X”的類型的純右值。 它不應出現在member-declarator的其他位置。5
this
的表達不得出現在任何其他上下文中。 [示例: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 }; } };
—結束示例]
考慮到這一點,下面的代碼:
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, "" );
}
通過Daniel Frey 的static_assert
。
除非類型需要是封閉類的成員類型,否則您可以將self
的使用替換為decltype(*this)
。 如果您在代碼中的許多地方使用它,您可以定義一個宏SELF
,如下所示:
#define SELF decltype(*this)
基於 hvd 的答案,我發現唯一缺少的是刪除引用,這就是 std::is_same 檢查失敗的原因(b/c 結果類型實際上是對該類型的引用)。 現在這個無參數宏可以完成所有工作。 下面的工作示例(我使用 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";
}
提供我的版本。 最好的一點是它的用法和原生類一樣。 但是,它不適用於模板類。
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); }
};
終於找到了合適的解決方案! 這個想法是由github 上的@MitalAshok 提出的。
#include <type_traits>
namespace SelfType
{
template <typename T>
struct Reader
{
friend auto adl_GetSelfType(Reader<T>);
};
template <typename T, typename U>
struct Writer
{
friend auto adl_GetSelfType(Reader<T>){return U{};}
};
inline void adl_GetSelfType() {}
template <typename T>
using Read = std::remove_pointer_t<decltype(adl_GetSelfType(Reader<T>{}))>;
}
#define DEFINE_SELF \
struct _self_type_tag {}; \
constexpr auto _self_type_helper() -> decltype(::SelfType::Writer<_self_type_tag, decltype(this)>{}, void()) {} \
using Self = ::SelfType::Read<_self_type_tag>;
然后:
struct A
{
DEFINE_SELF
static_assert(std::is_same_v<Self, A>);
};
這使用有狀態模板元編程將類型存儲在可訪問的上下文中(在輔助函數的尾隨返回類型中),然后讀取否則無法訪問的類型(在類范圍內)。
這里的關鍵是作者是如何被實例化的。 簡單地執行auto _self_type_helper() -> Writer<...> {return {};}
不起作用:編寫器被延遲實例化,使得狀態可以在任何成員函數體內訪問,但不是類范圍.
但是,如果您這樣做-> decltype(Writer<...>{}, void())
,或者以其他方式使編寫器成為影響類型的表達式的一部分,那么它就會開始工作。 我不完全確定為什么會這樣。
我將重復“必須自己做”的明顯解決方案。 這是代碼的簡潔 C++11 版本,它適用於簡單的類和類模板:
#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")
你可以在ideone看到它的實際效果。 導致此結果的起源如下:
#define DECLARE_SELF(Type) typedef Type _TySelf; /**< @brief type of this class */
struct XYZ {
DECLARE_SELF(XYZ)
};
將代碼復制粘貼到不同的類並忘記更改 XYZ 存在明顯的問題,如下所示:
struct ABC {
DECLARE_SELF(XYZ) // !!
};
我的第一種方法不是很原始 - 制作一個函數,如下所示:
/**
* @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
有點長,但請耐心等待。 這具有在沒有decltype
的 C++03 中工作的優點,因為使用__self_check_helper
函數來推斷this
的類型。 此外,沒有static_assert
,而是使用了sizeof()
技巧。 對於 C++0x,你可以讓它更短。 現在這不適用於模板。 此外,宏在末尾不期望分號還有一個小問題,如果使用 pedantic 編譯,它會抱怨額外的不必要的分號(或者您將在XYZ
的主體中留下一個看起來不以分號結尾的奇怪宏和ABC
)。
檢查傳遞給DECLARE_SELF
的Type
不是一個選項,因為這只會檢查XYZ
類(沒問題),而忽略了ABC
(有錯誤)。 然后它就撞到了我。 使用模板的無額外存儲零成本解決方案:
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
這只是對唯一的枚舉值進行靜態斷言(或者至少是唯一的,以防你沒有將所有代碼都寫在一行上),沒有使用類型比較技巧,它作為靜態斷言工作,即使在模板中也是如此. 作為獎勵 - 現在需要最后的分號:)。
感謝Yakk給了我很好的啟發。 如果沒有先看到他的答案,我不會寫這個。
使用 VS 2008 和 g++ 4.6.3 測試。 事實上,對於XYZ
和ABC
示例,它抱怨:
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>â
現在,如果我們將 ABC 設為模板:
template <class X>
struct ABC {
DECLARE_SELF(XYZ); // line 92
};
int main(int argc, char **argv)
{
ABC<int> abc;
return 0;
}
我們將得到:
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>â
僅觸發了行號檢查,因為未編譯函數檢查(如預期的那樣)。
使用 C++0x(並且沒有邪惡的下划線),您只需要:
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
我相信 CStaticAssert 位令人遺憾地仍然是必需的,因為它產生了一個類型,該類型在模板主體中進行了類型定義(我想同樣不能用static_assert
完成)。 這種方法的優點仍然是它的零成本。
我對這些古怪的模板一無所知,超級簡單的東西怎么樣:
#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
工作完成,除非你不能忍受幾個宏。 您甚至可以使用CLASSNAME
來聲明您的構造函數(當然還有析構函數)。
現場演示。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.