簡體   English   中英

我可以在 C++ 中實現自主的“self”成員類型嗎?

[英]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)中允許,
  • 非靜態數據成員的類型不能從初始化程序([dcl.spec.auto]p5)推導出來,
  • 非靜態成員函數只能在函數調用的上下文中由非限定名稱引用 ([expr.ref]p4)
  • 非靜態成員函數只能通過非限定名稱調用,即使在未評估的上下文中,當this可以使用時([over.call.func]p3),
  • 通過限定名稱或成員訪問對非靜態成員函數的引用需要對正在定義的類型的引用

我想我可以肯定地說,如果不以某種方式、某處包含類型名稱,就根本無法實現self

編輯:我之前的推理存在缺陷。 “非靜態成員函數只能通過非限定名稱調用,即使在未評估的上下文中,當 this 可以使用時([over.call.func]p3)”是不正確的。 實際上說的是

如果關鍵字this (9.3.2) 在范圍內並引用類TT的派生類,則隱含的對象參數是(*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_SELFType不是一個選項,因為這只會檢查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 測試。 事實上,對於XYZABC示例,它抱怨:

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM