Зарегистрировать переменную-член внутри определения класса

#c #compiler-errors

#c #ошибки компилятора

Вопрос:

Мне было интересно, есть ли способ зарегистрировать переменную внутри класса, в этом случае просто введите имя переменной.

 #include <iostream>

void register_variable(const char* name) { std::cout <<'n' << name << 'n'; }

#define VARIABLE(connector) 
    connector; 
    register_variable(#connector);

class Test {
public:
    VARIABLE(int b) // does not compile
};

int main()
{    
    VARIABLE(int a) // works
}
  

Следующий код выдает эту ошибку компиляции:

 main.cpp:11:19: error: expected identifier before string constant
     VARIABLE(int b) // does not compile
                   ^
main.cpp:7:24: note: in definition of macro ‘VARIABLE’
     register_variable(#connector);
                        ^~~~~~~~~
main.cpp:11:19: error: expected ‘,’ or ‘...’ before string constant
     VARIABLE(int b) // does not compile
                   ^
main.cpp:7:24: note: in definition of macro ‘VARIABLE’
     register_variable(#connector);
                        ^~~~~~~~~
main.cpp:7:33: error: ISO C   forbids declaration of ‘register_variable’ with no type [-fpermissive]
     register_variable(#connector);
                                 ^
main.cpp:11:5: note: in expansion of macro ‘VARIABLE’
     VARIABLE(int b) // does not compile
     ^~~~~~~~
  

Я понимаю, что вызов функции внутри определения класса запрещен, но, возможно, есть хитрость, как это сделать.

Комментарии:

1. Почему #connector ?

2. проблема xy ? Зачем вам нужно регистрировать определения переменных-членов?

3. Я работаю над игровым движком и хочу иметь возможность автоматически регистрировать / сериализовать переменные, помеченные макросом. Подумайте [SerializeField] из Unity.

Ответ №1:

Ваш подход не работает, потому что вы не можете вызвать функцию в объявлении класса. Ваш макрос расширяется до:

 class Test {
public:
   int b;
   register_variable("int b"); // This isn't allowed here, it should be in a constructor
};
  

Существует несколько решений для выполнения того, что вы намереваетесь, но для этого требуется больше кода.

Идеальным решением было бы дождаться C 21 или более поздней версии, когда истинное отражение будет принято в стандарте. В этом случае вам нужно будет запросить тип с помощью кода шаблона, чтобы получить имя всех членов, а затем вызвать что-то вроде cout << meta<Test>::get_member<0>(testInstance)

К сожалению, пока это не настроено в stone, вам нужно полагаться только на некоторые другие приемы, например, на один из двух, которые я показываю ниже:

Хитрость 1

Разделите объявление и определение в 2 макросах вместо одного, вот так:

 #define DeclareVariable(X)   X
#define DefineVariable(X)    register_variable(#X)

// Used like
struct Test {
   DeclareVariable(int b);
   Test() {
     DefineVariable(b);
   }
};
  

Очевидно, что вам может потребоваться нечто большее, чем эти очень простые бесполезные макросы, например, связывание имени переменной с указателем на переменную через хэш-таблицу (это делается в DefineVariable макросе). Это довольно легко сделать, я не буду это показывать.

Недостатком этого метода является то, что техническое обслуживание является болезненным. Тип переменной должен обрабатываться там, где он используется (это означает, что если вы хотите объявить a float X , a MyStruct Y …), вам понадобится a register_variable , который также может работать с этими типами.

Трюк 2 (использование шаблона)

Я предполагаю, что вы хотите реализовать какое-то отражение в своем тесте класса. Как вы делаете это в целом, чтобы оно не сломалось при первом незначительном изменении?

Сначала давайте объясним, как это работает:

  1. Я использую тот факт, что все имена членов разные
  2. Можно специализировать шаблон на основе адреса функции
  3. Самоанализ выполняется с помощью таблицы метаданных о ваших членах
  4. Эта таблица является статической для класса, поэтому она хранится как статическая структура указателей на шаблонные функции (обычно ее называют виртуальной таблицей)

Вот как я бы это сделал (котельная плита):

 struct MemberBase
{
    void * ptr;
    const char * type;
    const char * name;
    
    MemberBase(void * ptr, const char * type, const char * name) : ptr(ptr), type(type), name(name) {}
    virtual ~MemberBase();
    virtual void set_virt(const void * u);
    virtual void get_virt(void * u);
};

template <typename U>
struct Member : public MemberBase
{
   Member() : MemberBase(0, typename(U), 0) {}
   Member(U amp; u, const char * type, const char * name) : MemberBase(amp;u, type, name) {}

   void set(const U amp; u) { *static_cast<U*>(ptr) = u; }
   void get(U amp; u) const { if (ptr) u = *static_cast<U*>(ptr); }
   
   void set_virt(const void * u) { if (ptr) set(static_cast<const U*>(u)); }
   void get_virt(void * u)       { get(static_cast<U*>(u)); }
 
};

template <typename U>
struct Introspect
{
   typedef U ClassType;
   struct VirtualTable
   {
       const char * (*get_name)();
       const char * (*get_type)();
       MemberBase * (*get_ref)(U amp; object);

       void (*get)(U amp; object, void * value);
       void (*set)(U amp; object, const void * value);
   };

   template <typename Member, Member amp; (T::*Method)(), const char * (*Name)(), const char *(*Type)()>
   struct VirtualTableImpl
   {
      static const char * get_name()                           { return (*Name)(); }
      static const char * get_type()                           { return (*Type)(); }
      static MemberBase* get_ref(T amp; object)           { return amp;(object.*Method)(); }
      static void get(T amp; object, void * _args)               { (object.*Method)().get_virt(_args); }
      static void set(T amp; object, const void * _args)         { (object.*Method)().set_virt(_args); }
   };
   
   template <typename Member, Member amp; (T::*Method)(), const char * (*Name)(), const char *(*Type)()>
   static VirtualTable amp; get_member_table()
   {
       static VirtualTable table =
       {
           amp;VirtualTableImpl<Member, Method, Name, Type>::get_name,
                amp;VirtualTableImpl<Member, Method, Name, Type>::get_type,
                amp;VirtualTableImpl<Member, Method, Name, Type>::get_reference,
                amp;VirtualTableImpl<Member, Method, Name, Type>::get,
                amp;VirtualTableImpl<Member, Method, Name, Type>::set,              
       };
       return table;
   }

private:
   // We only need to register once
   static bool amp; registered_already() { static bool reg = false; return reg; }

public:
   typedef std::vector<VirtualTable> MembersMeta;
   // Get the members for this type
   static MembersMeta amp; get_members() { static MembersMeta mem; return mem; }

   // Register a member
        template <typename Member, Member amp; (T::*Method)(), const char * (*Name)(), const char * (*Type)()>
        static void register_member()
        {
            if (registered_already()) return;
            MembersMeta amp; mem = get_members();
            if (mem.size() amp;amp; strcmp(mem[0]->getName(), Name()) == 0)
                registered_already() = true;
            else mem.push_back(amp;get_member_table<Member, Method, Name, Type>());
        }
static size_t find_member(const char * name)
        {
            MembersMeta amp; mem = get_members();
            if (!name) return mem.size();
            for (size_t i = 0; i < mem.size(); i  )
                if (mem[i]->get_name() amp;amp; strcmp(mem[i]->get_name(), name) == 0) return i;
            return mem.size();
        }        
        virtual MemberBase * get_member(const size_t i)
        {
            Tamp; t = static_cast<Tamp;>(*this);
            MemberBase * h = get_members()[i]->get_ref(t);
            return h;
        }
};

#define CONCAT(X, Y, Z) X ## Y ## Z
#define DeclareMember(Type, Name)   Type Name; 
   Member<Type> prop_##Name; 
   inline Member<Type> amp; get_prop_##Name () { return prop_##Name; } 
   inline static const char * CONCAT(get_,Name,_name)() { return #Name; } 
   inline static const char * CONCAT(get_,Name,_type)() { return #Type; } 
   RegisterMember<Member<PropertyType>, ClassType, amp;ClassType::get_prop_##Name , amp;CONCAT(get_,Name,_name), amp;CONCAT(get_,Name,_type)> _regProp_##Name ; 
    

  #define DefineMember(X) prop_#X(X, CONCAT(get_,X,_type)(), CONCAT(get_,X,_name)()) 

  template <typename Mem, typename Obj, Mem amp; (Obj::*Method)(), const char * (*Name)(), const char * (*Type)()>
    struct RegisterMember
    {
        RegisterMember()
        {
            Introspect<Obj>::template register_member<Prop, Method, Name, Type>();
        }
    }; 
  

Затем использование (надеюсь, это намного проще в использовании:

 struct Test : public Introspect<Test>
{
    DeclareMember(int, x);

    Test(int a) : x(a), DefineMember(x) 
    { 
    }
};

int main(...) {
   Test t(2);
   // Usual usage: t.x is an true int member
   t.x = 42; 


   // Indirect usage: check if a member exist
   size_t i = t.find_member("x");
   if (i != t.get_members().size()) {
      // Capture t member
      MemberBase * member = t.get_member(i);
      
      // If you know the type (else you can try a dynamic_cast here)
      Member<int> * mem_int = static_cast<Member<int>*>(member);
      int ret = 0;
      mem_int->get(ret);
      std::cout << member->type << " " << member->name << ": "<< ret << std::endl; // int x: 42
      
      // If you don't know the type, it's harder and unsafe
      member->get_virt(amp;ret);
      std::cout << "generic: " << ret << std::endl; // generic: 42

      // Example without a dynamic_cast
      if (strcmp(member->type, "int") == 0) {
         Member<int> * mem_int = static_cast<Member<int>*>(member);
         mem_int->set(18);
      }

      // Example with a dynamic cast
      if (Member<float> * f = dynamic_cast<Member<float>*>(member)) {
         f->set(3.14f);
         std::cout<< "Will never get here since it's a int" << std::endl;
      }
   }
   return 0;
}

  

Комментарии:

1. Я был бы очень благодарен, если бы вы могли рассказать мне больше об этих решениях.

2. Вот оно. Наслаждайтесь!