Is there a way to get pointer-to-member without mentioning the class name if it is used within the context of the class

162 views Asked by At

The code below gives the following compilation error:

main.cpp:5:48: error: invalid use of non-static data member 'id'
    static constexpr int Type::* mem_ptr_id = &id;
                                               ^~
main.cpp:5:34: error: default initialization of an object of const type 'int Type::*const'
    static constexpr int Type::* mem_ptr_id = &id;
                                 ^
                                            = nullptr
main.cpp:6:46: error: invalid use of non-static data member 'id'
    static constexpr auto mem_ptr_id_auto = &id;
                                             ^~
main.cpp:6:27: error: declaration of variable 'mem_ptr_id_auto' with deduced type 'const auto' requires an initializer
    static constexpr auto mem_ptr_id_auto = &id;
                          ^
4 errors generated.

which is kind of expected.

#include <iostream>

struct Type {
    int id;
    static constexpr int Type::* mem_ptr_id = &id; // &Type::id required
    static constexpr auto mem_ptr_id_auto = &id; // &Type::id required
};

int main() {
    Type test;
    test.*Type::mem_ptr_id = 5; // same as test.id = 5
    test.*Type::mem_ptr_id_auto = 5; // same as test.id = 5
    std::cout << test.id << std::endl; // expected: 5
    return 0;
}

I need a way to have static pointer-to-member variable of my class/structure without explicitly naming the type (class/struct) name. Any suggestions?

Note: In order to avoid auto to become int&, I did pointer-to-member wrapper:

template<typename T, class E>
struct Pointer2Member {
    using var_t = T;
    using entity_t = E;
    using mem_ptr_t = T E::*;

    T E::* data;

    constexpr Pointer2Member() {}

    constexpr Pointer2Member(T E::* val) : data(val) {}

    constexpr operator T E::* () {
        return data;
    }
};

template<auto ptr>
struct Pointer2MemberOf;

template<typename T, class E, T E::* ptr>
struct Pointer2MemberOf<ptr> : Pointer2Member<T, E> {
    constexpr Pointer2MemberOf() : Pointer2Member<T, E>(ptr) {}

    constexpr operator Pointer2Member<T, E>() {
        return *this;
    }
};

struct Type {
    int id;
    static constexpr auto mem_ptr_id = Pointer2MemberOf<&id>(); // Pointer2MemberOf<&Type::id>() required
};

but it gives same error:

main.cpp:34:58: error: invalid use of non-static data member 'id'
    static constexpr auto mem_ptr_id = Pointer2MemberOf<&id>();
                                                         ^~
main.cpp:34:27: error: declaration of variable 'mem_ptr_id' with deduced type 'const auto' requires an initializer
    static constexpr auto mem_ptr_id = Pointer2MemberOf<&id>();
                          ^
main.cpp:40:17: error: no member named 'mem_ptr_id_auto' in 'Type'
    test.*Type::mem_ptr_id_auto = 5; // same as test.id = 5
          ~~~~~~^
3 errors generated.

Note 2: Follow-up on the comment "What do you need this for":

Unfortunately, it is very complicated variadic template solution which I can't talk a lot about.

What I want to achieve is to create a template class Result<...> which can store custom member variables of different classes.

Result<User::id, User::username, Post::id> and Result<User, Post::id> should both be viable syntaxes and should have Result<User, Post::id>::get<PROPERTY> which should be able to have both Result<User, Post::id>::get<User> and Result<User, Post::id>::get<User::id> (yes, User, not Post).

Imagine Result<...> class in a library that will be used by beginner C++ programmers so I don't want to use the &User::id syntax as it might be too complicated to comprehend for them.

Also, the static member will be auto-generated via macros like

#define Member(TYPE, NAME) TYPE _##NAME; static constexpr auto NAME = Pointer2Member(&_##NAME)

struct User {
    Member(int, id);
    Member(std::string, username);
};

Result<User::id> result1;
result1.get<User::id>() = 5;

Result<User> result2;
result2.get<User::id>() = 6;
result2.get<User::username>() = "John";
1

There are 1 answers

0
p0358 On

Yes, but not in static. Generally you can replace the reference to class/struct name such as Test by std::remove_reference_t<decltype(*this)>, but only if this is available.

For example you can replace decltype(&Test::somefunc) by decltype(&std::remove_reference_t<decltype(*this)>::somefunc), which can be useful in templates.

If you modify your example to something like this, then it will also work:

#include <iostream>

struct Type {
    int id;
    int Type::* mem_ptr_id = &std::remove_reference_t<decltype(*this)>::id; // same as &Type::id
};

int main() {
    Type test;

    test.*test.mem_ptr_id = 5;

    int Type::* mem_ptr_id_local = &std::remove_reference_t<decltype(test)>::id;
    test.*mem_ptr_id_local = 5;

    auto mem_ptr_id_auto = &std::remove_reference_t<decltype(test)>::id;
    test.*mem_ptr_id_auto = 5;

    std::cout << test.id << std::endl; // expected: 5
    return 0;
}

As you can see none of those are static anymore (mem_ptr_id has default initialization), and access to mem_ptr_id is a bit awkward. But otherwise yes, it is possible in this way as long as this is available, and it can be very useful for templates/macros. Minimum C++14 required.


Also just for fun I will paste an example code snippet where it can be useful in practice:

template <typename T> struct getvfunc_wrapper_autotype;
template<typename T, typename RT, typename... Args>
struct getvfunc_wrapper_autotype<RT(T::*)(Args...)>
{
    typedef RT(__fastcall* FT)(PVOID, Args...);
    inline static FT get(const void* inst, size_t index, size_t offset = 0)
    {
        return reinterpret_cast<FT>(getvtable(inst, offset)[index]);
    }
};

#define DECLARE_VFUNC(index, return_type, fname, arg_types, arg_invokation) \
inline return_type fname arg_types \
{ \
    auto* _ = this; \
    return getvfunc_wrapper_autotype< decltype(&std::remove_reference_t<decltype(*this)>:: fname ) >::get(_, index) arg_invokation; \
}

class IVEngineClient
{
public:
    void* m_vtable;
    DECLARE_VFUNC(18, void, GetScreenSize, (int& wide, int& tall), (_, wide, tall)
    DECLARE_VFUNC(21, void, ClientCmd, (const char* szCmdString), (_, szCmdString))
};

As we can see, in the macro above it would be quite annoying to have to pass IVEngineClient as one of the parameters manually every time. And this trick does well to avoid that.