#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 (использование шаблона)
Я предполагаю, что вы хотите реализовать какое-то отражение в своем тесте класса. Как вы делаете это в целом, чтобы оно не сломалось при первом незначительном изменении?
Сначала давайте объясним, как это работает:
- Я использую тот факт, что все имена членов разные
- Можно специализировать шаблон на основе адреса функции
- Самоанализ выполняется с помощью таблицы метаданных о ваших членах
- Эта таблица является статической для класса, поэтому она хранится как статическая структура указателей на шаблонные функции (обычно ее называют виртуальной таблицей)
Вот как я бы это сделал (котельная плита):
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. Вот оно. Наслаждайтесь!