Есть ли способ получить указатель на член без упоминания имени класса, если он используется в контексте класса

Код ниже дает следующую ошибку компиляции:

      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.

что вроде ожидаемо.

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

Мне нужен способ иметь статическую переменную-указатель на член моего класса/структуры без явного указания имени типа (класса/структуры) . Какие-либо предложения?

Примечание: во избежаниеautoстановитьсяint&, я сделал оболочку указателя на член:

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

но выдает ту же ошибку:

      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.

Примечание 2: продолжение комментария «Для чего вам это нужно»:

К сожалению, это очень сложное решение для вариативных шаблонов, о котором я не могу много говорить.

Чего я хочу добиться, так это создать класс шаблона, который может хранить пользовательские переменные-члены разных классов.

Result<User::id, User::username, Post::id>иResult<User, Post::id>оба должны быть жизнеспособными синтаксисами и должны иметьResult<User, Post::id>::get<PROPERTY>который должен иметь возможность иметь обаResult<User, Post::id>::get<User>иResult<User, Post::id>::get<User::id>(да,User, нетPost).

Представлять себеResult<...>class в библиотеке, которую будут использовать начинающие программисты на C++, поэтому я не хочу использовать&User::idсинтаксис, так как он может быть слишком сложным для их понимания.

Кроме того, статический элемент будет автоматически сгенерирован с помощью макросов, таких как

      #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 ответ

Да, но не в статике. Обычно вы можете заменить ссылку на имя класса/структуры, напримерTestкstd::remove_reference_t<decltype(*this)>, но только если имеется.

Например, вы можете заменитьdecltype(&Test::somefunc)кdecltype(&std::remove_reference_t<decltype(*this)>::somefunc), что может быть полезно в шаблонах.

Если вы измените свой пример примерно так, он также будет работать:

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

Как вы можете видеть, ни один из них больше не является статичным (mem_ptr_id имеет инициализацию по умолчанию), и доступ кmem_ptr_idэто немного неловко. Но в противном случае да, это возможно, покаthisдоступен и может быть очень полезен для шаблонов/макросов. Требуется минимум C++14.


Также ради интереса я вставлю пример фрагмента кода, где он может пригодиться на практике:

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

Как мы видим, в приведенном выше макросе было бы весьма досадно передаватьIVEngineClientв качестве одного из параметров каждый раз вручную. И этот трюк помогает избежать этого.

Другие вопросы по тегам