Can I implement an autonomous `self` member type i

2019-01-01 07:02发布

C++ lacks the equivalent of PHP's self keyword, which evaluates to the type of the enclosing class.

It's easy enough to fake it on a per-class basis:

struct Foo
{
   typedef Foo self;
};

but I had to write Foo again. Maybe I'll get this wrong one day and cause a silent bug.

Can I use some combination of decltype and friends to make this work "autonomously"? I tried the following already but this is not valid in that place:

struct Foo
{
   typedef decltype(*this) self;
};

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

(I'm not going to worry about the equivalent of static, which does the same but with late binding.)

标签: c++ c++11
13条回答
荒废的爱情
2楼-- · 2019-01-01 07:29

I recently discovered that *this is allowed in a brace-or-equal-initializer. Described in § 5.1.1 (from the n3337 working draft):

3 [..] Unlike the object expression in other contexts, *this is not required to be of complete type for purposes of class member access (5.2.5) outside the member function body. [..]

4 Otherwise, if a member-declarator declares a non-static data member (9.2) of a class X, the expression this is a prvalue of type “pointer to X” within the optional brace-or-equal-initializer. It shall not appear elsewhere in the member-declarator.

5 The expression this shall not appear in any other context. [ Example:

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

end example ]

With that in mind, the following code:

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

passes Daniel Frey's static_assert.

Live example

查看更多
骚的不知所云
3楼-- · 2019-01-01 07:36
#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
};

this does not work on template types, as self_check is not called, so the static_assert is not evaluated.

We can do some hacks to make it work for templates as well, but it has a minor run time cost.

#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

an empty struct of size 1 byte is created in your class. If your type is instantiated, self is tested against.

查看更多
明月照影归
4楼-- · 2019-01-01 07:38

You can use a macro instead of a regular class declaration, that will do that for you.

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

And then use like

CLASS_WITH_SELF(Foo) 
};

#define END_CLASS }; would probably help readability.


You could also take @Paranaix's Self and use it (it starts to get really hackish)

#define WITH_SELF(X) X : public Self<X>

class WITH_SELF(Foo) {
};
查看更多
心情的温度
5楼-- · 2019-01-01 07:41

I will repeat the obvious solution of "having to do it yourself". This is the succinct C++11 version of the code, which works with both simple classes and class templates:

#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")

You can see it in action at ideone. The genesis, leading to this result is below:

#define DECLARE_SELF(Type) typedef Type _TySelf; /**< @brief type of this class */

struct XYZ {
    DECLARE_SELF(XYZ)
};

This has the obvious problem with copy-pasting the code to a different class and forgetting to change XYZ, like here:

struct ABC {
    DECLARE_SELF(XYZ) // !!
};

My first approach was not very original - making a function, like this:

/**
 *  @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

It is kind of lengthy, but please bear with me here. This has the advantage of working in C++03 without decltype, as the __self_check_helper function is employed to deduce type of this. Also, there is no static_assert, but the sizeof() trick is employed instead. You could make it much shorter for C++0x. Now this will not work for templates. Also, there is a minor issue with the macro not expecting semicolon at the end, if compiling with pedantic, it will complain about an extra unnecessary semicolon (or you will be left with an odd looking macro not ending in semicolon in the body of XYZ and ABC).

Making a check on the Type that is passed to DECLARE_SELF is not an option, as that would only check the XYZ class (which is ok), oblivious to ABC (which has error). And then it hit me. A no-additional storage zero-cost solution that works with templates:

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 

This simply makes static assertion on a unique enum value (or at least unique in case you don't write all of your code on a single line), no type-comparing trickery is employed, and it works as static assert, even in templates. And as a bonus - the final semicolon is now required :).

I'd like to thank Yakk for giving me a good inspiration. I wouldn't write this without first seeing his answer.

Tested with VS 2008 and g++ 4.6.3. Indeed, with the XYZ and ABC example, it complains:

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>â

Now if we make ABC a template:

template <class X>
struct ABC {
    DECLARE_SELF(XYZ); // line 92
};

int main(int argc, char **argv)
{
    ABC<int> abc;
    return 0;
}

We will get:

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>â

Only the line-number check triggered, as the function check was not compiled (as expected).

With C++0x (and without the evil underscores), you would need just:

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

I believe that the CStaticAssert bit is regrettably still required as it produces a type, which is typedef-ed in the template body (i suppose the same cannot be done with static_assert). The advantage of this approach is still its zero cost.

查看更多
高级女魔头
6楼-- · 2019-01-01 07:42

What works in both GCC and clang is to create a typedef that refers to this by using this in the trailing-return-type of a function typedef. Since this is not the declaration of a static member function, the use of this is tolerated. You can then use that typedef to define self.

#define DEFINE_SELF() \
    typedef auto _self_fn() -> decltype(*this); \
    using self = decltype(((_self_fn*)0)())

struct Foo {
    DEFINE_SELF();
};

struct Bar {
    DEFINE_SELF();
};

Unfortunately, a strict reading of the standard says that even this is not valid. What clang does is check that this is not used in the definition of a static member function. And here, it indeed isn't. GCC doesn't mind if this is used in a trailing-return-type regardless of the sort of function, it allows it even for static member functions. However, what the standard actually requires is that this is not used outside of the definition of a non-static member function (or non-static data member initialiser). Intel gets it right and rejects this.

Given that:

  • this is only allowed in non-static data member initialisers and non-static member functions ([expr.prim.general]p5),
  • non-static data members cannot have their type deduced from the initialiser ([dcl.spec.auto]p5),
  • non-static member functions can only be referred to by an unqualified name in the context of a function call ([expr.ref]p4)
  • non-static member functions can only be called by unqualified name, even in unevaluated contexts, when this can be used ([over.call.func]p3),
  • a reference to a non-static member function by qualified name or member access requires a reference to the type being defined

I think I can conclusively say that there is no way at all to implement self without including in some way, somewhere, the type name.

Edit: There is a flaw in my earlier reasoning. "non-static member functions can only be called by unqualified name, even in unevaluated contexts, when this can be used ([over.call.func]p3)," is incorrect. What it actually says is

If the keyword this (9.3.2) is in scope and refers to class T, or a derived class of T, then the implied object argument is (*this). If the keyword this is not in scope or refers to another class, then a contrived object of type T becomes the implied object argument. If the argument list is augmented by a contrived object and overload resolution selects one of the non-static member functions of T, the call is ill-formed.

Inside a static member function, this may not appear, but it still exists.

However, per the comments, inside a static member function, the transformation of f() to (*this).f() would not be performed, and it that isn't performed, then [expr.call]p1 is violated:

[...] For a member function call, the postfix expression shall be an implicit (9.3.1, 9.4) or explicit class member access (5.2.5) whose [...]

as there would be no member access. So even that wouldn't work.

查看更多
步步皆殇っ
7楼-- · 2019-01-01 07:44

A possible workaround (as you still have to write the type once):

template<typename T>
struct Self
{
protected:
    typedef T self;
};

struct Foo : public Self<Foo>
{
    void test()
    {
        self obj;
    }
};

For a more safer version we could assure that T actually derives from Self<T>:

Self()
{
    static_assert(std::is_base_of<Self<T>, T>::value, "Wrong type passed to Self");
}

Notice that a static_assert inside a member function is probably the only way to check, as types passed tostd::is_base_of have to be complete.

查看更多
登录 后发表回答