我可以在C ++中实现一个自治的`self`成员types吗?

C ++ 缺乏 PHP的self关键字的等价物,它的计算结果是封闭类的types。

以每class为基础很容易伪造:

 struct Foo { typedef Foo self; }; 

但是我不得不再次写Foo 。 也许我有一天会得到这个错误,并导致一个沉默的错误。

我可以使用一些decltype和朋友的组合来使这项工作“自主”吗? 我已经尝试了以下内容,但在这个地方无效:

 struct Foo { typedef decltype(*this) self; }; // main.cpp:3:22: error: invalid use of 'this' at top level // typedef decltype(*this) self; 

(我不会担心static ,相同的,但后期绑定。)

以下是您可以在不重复Footypes的情况下执行此操作的方法:

 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派生,那么你应该用下面的方法使用macrosWITH_SELF_DERIVED

 class WITH_SELF_DERIVED(Bar,Foo) { /* ... */ }; 

你甚至可以用你想要的基类来完成多重inheritance(感谢可变参数模板和可变参数macros):

 class WITH_SELF(Foo2) { /* ... */ }; class WITH_SELF_DERIVED(Bar2,Foo,Foo2) { /* ... */ }; 

我已经validation了这个工作在gcc 4.8和叮当3.4。

你可以使用macros而不是常规的类声明,这将为你做。

 #define CLASS_WITH_SELF(X) class X { typedef X self; 

然后使用像

 CLASS_WITH_SELF(Foo) }; 

#define END_CLASS }; 可能会有助于可读性。


你也可以采取@ Paranaix的Self并使用它(它开始变得非常黑客)

 #define WITH_SELF(X) X : public Self<X> class WITH_SELF(Foo) { }; 

一个可能的解决方法(因为你仍然必须写一次types):

 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_oftypes必须完整。

我没有确凿的证据,但我认为这是不可能的。 以下失败 – 出于同样的原因,我认为这是我们可以得到的最远的:

 struct Foo { auto self_() -> decltype(*this) { return *this; } using self = decltype(self_()); }; 

从本质上讲,这表明我们想要声明typedef的范围根本就没有访问权限(不pipe是直接的还是间接的),并且没有其他(与编译器无关的)获取类的types或名称的方法。

在GCC和clang中都起作用的是通过在函数typedef的结尾返回types中使用this来创build一个typedef。 由于这不是静态成员函数的声明,所以可以使用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(); }; 

不幸的是,严格阅读标准说,即使这是无效的。 铿锵做的是检查this是不是在静态成员函数的定义中使用。 而在这里,事实并非如此。 GCC不介意,如果在尾随返回types中使用它,而不考虑函数的types,它甚至允许static成员函数。 但是,标准实际要求的是, this不是在非静态成员函数(或非静态数据成员初始化程序)的定义之外使用的。 英特尔做对了,拒绝这个。

鉴于:

  • 这只允许在非静态数据成员初始化和非静态成员函数([expr.prim.general] p5)中使用,
  • 非静态数据成员不能从初始化([dcl.spec.auto] p5)推导出它们的types,
  • 非静态成员函数只能在函数调用的上下文中使用非限定名称引用([expr.ref] p4)
  • 非静态成员函数只能通过非限定名称来调用,即使在未经评估的上下文中,当可以使用([over.call.func] p3)时,
  • 通过限定名称或成员访问对非静态成员函数的引用需要引用所定义的types

我想我可以决定性地说,没有任何方式来实现self而不以某种方式,某处,types名称。

编辑 :我早先的推理有一个缺陷。 “非静态成员函数只能通过非限定名称调用,即使在未评估的上下文中,当可以使用([over.call.func] p3)时,”是不正确的。 它实际上说的是

如果关键字this (9.3.2)在范围内并且指向类TT的派生类,那么隐含的对象参数是(*this) 。 如果关键字this不在范围内或者引用另一个类,那么Ttypes的人为对象就成为隐含的​​对象参数。 如果参数列表被增加了一个被devise的对象,并且重载决议select了T的非静态成员函数之一,则调用是不合格的。

在一个静态成员函数内部, this可能不会出现,但是它依然存在。

然而,根据注释,在一个静态成员函数中, f()(*this).f()不会被执行,并且不被执行,那么违反了[expr.call] p1:

[…]对于成员函数调用,后缀expression式应该是一个隐式(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 }; 

这在模板types上不起作用,因为self_check没有被调用,所以static_assert不被计算。

我们可以做一些黑客工作,以使其为template s,但它有一个小的运行时间成本。

 #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 

在您的类中创build一个大小为1字节的空struct 。 如果你的types被实例化,那么self就会被testing。

我也认为这是不可能的,这是另一个失败,但恕我直言,有趣的尝试,避免了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 ++要求你用class来限定self_f ,当你想把它的地址:(

我最近发现*this是允许在括号或等于初始值设定项 。 在§5.1.1来自n3337工作草案 )中描述:

3 [..]与其他上下文中的对象expression式不同, *this不需要成员函数体外的类成员访问(5.2.5)的完整types。 [..]

4否则,如果一个成员声明符声明了一个X类的非静态数据成员(9.2),那么expression式就是在可选的括号或等于初始值设定项中的一个types为“指向X的指针”的prvalue。 它不应出现在成员申报人的其他地方。

5这一表述不应出现在任何其他情况下。 [ 例如:

 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

现场示例

除非types需要是封闭类的成员types,否则可以用decltype(*this)来replace自己的使用。 如果你在你的代码中的很多地方使用它,你可以定义一个macrosSELF ,如下所示:

 #define SELF decltype(*this) 

提供我的版本。 最好的事情是,它的使用是相同的本土类。 但是,它不适用于模板类。

 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); } }; 

我将重复“必须自己做”的明显解决办法。 这是代码的简洁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) // !! }; 

我的第一个方法不是非常原创的 – 制作一个function,就像这样:

 /** * @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函数来推断thistypes。 另外,没有static_assert ,但是使用sizeof()技巧。 对于C ++ 0x,你可以使它更短。 现在这不适用于模板。 此外,还有一个小问题macros不期望在最后分号,如果编辑迂腐,它会抱怨一个额外的不必要的分号(或者你会留下一个奇怪的macros观不是以分号在XYZABC )。

对传递给DECLARE_SELFType进行检查不是一个选项,因为这样只会检查XYZ类(没关系),不理会ABC (有错误)。 然后它就撞到了我。 与模板配合使用的无需额外存储的零成本解决scheme:

 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 

这只是静态断言一个独特的枚举值(或者至less是独一无二的,如果你不把所有的代码写在一行上),没有使用types比较的技巧,而且它作为静态断言,即使在模板。 作为奖励 – 最后的分号现在需要:)。

我想感谢Yakk给我一个很好的启发。 没有先看到他的回答,我不会写这个。

使用VS 2008和g ++ 4.6.3进行testing。 的确,以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>â 

只有行号检查被触发,因为function检查没有被编译(如预期的那样)。

用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位是令人遗憾的仍然需要,因为它产生一个typedef-ed在模板正文(我想同样不能用static_assert完成)。 这种方法的优点仍然是零成本。