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:23

I have no positive evidence but I think it’s impossible. The following fails – for the same reason as your attempt – and I think that’s the furthest we can get:

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

    using self = decltype(self_());
};

Essentially, what this demonstrates is that the scope at which we want to declare our typedef simply has no access (be it direct or indirect) to this, and there’s no other (compiler independent) way of getting to the class’ type or name.

查看更多
牵手、夕阳
3楼-- · 2019-01-01 07:24

I don't know all about these wacky templates, how about something super-simple:

#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

Job done, unless you can't stand a couple of macros. You can even use CLASSNAME to declare your constructor(s) (and, of course, destructor).

Live demo.

查看更多
与风俱净
4楼-- · 2019-01-01 07:25

Building upon the answer by hvd, I found that the only thing that was missing was removing the reference, that is why the std::is_same check fails (b/c the resulting type is actually a reference to the type). Now this parameter-less macro can do all the work. Working example below (I use 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";
}
查看更多
浅入江南
5楼-- · 2019-01-01 07:27

Provide my version. The best thing is that its use is the same as the native class. However, it doesn't work for template classes.

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); }
};
查看更多
浪荡孟婆
6楼-- · 2019-01-01 07:28

Here's how you can do it without repeating the type 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;
    }
};

If you want to derive from Foo then you should use the macro WITH_SELF_DERIVED in the following way:

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

You can even do multiple inheritance with as many base classes as you want (thanks to variadic templates and variadic macros):

class WITH_SELF(Foo2)
{
    /* ... */
};

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

I have verified this to work on gcc 4.8 and clang 3.4.

查看更多
不再属于我。
7楼-- · 2019-01-01 07:29

I also think it's impossible, here's another failed but IMHO interesting attempt which avoids the this-access:

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

which fails because C++ requires you to qualify self_f with the class when you want to take it's address :(

查看更多
登录 后发表回答