vendredi 12 mai 2023

Why is reflection in c++ considered impossible? [duplicate]

I have seen a lot of misinformation about C++ not having out-of-the-box reflection and if you do want full reflection, you must use other languages, install libraries or give up.

But... It seems like c++ supports reflection just fine. I can relatively simply add a system that prints out even a private variables' name, value and type out in c++11. It can also be used to check if a variable of certain name and type exists on a class. Am I misunderstanding what reflections means in this context, or is stackoverflow consensus just incorrect?

Code example:

Factory.h, basic factory with object registry

#pragma once

#include <cstring> // strcmp
#include <functional>
#include <string>
#include <unordered_map>

#include "Object.h"

// Allows Object to be spawnable through SpawnObject.
// Registers the class with the factory's registry, and connects that to a templated SpawnObject function
#define SPAWNABLE(CLASSNAME) class CLASSNAME; static bool CLASSNAME##Registered = (ObjectFactory::GetInstance().GetObjectRegistry()[#CLASSNAME] = &ObjectFactory::SpawnObject<CLASSNAME>, true)

// Spawns a class of selected name
#define DoSpawn ObjectFactory::GetInstance().SpawnObjectByName

// Find a class of selected name
#define FindObject ObjectFactory::GetInstance().FindObjectByName

// Singleton  object factory
class ObjectFactory
{
public:
    std::unordered_multimap<std::string, Object*> Objects;

    // Gets instance of the simpleton
    static ObjectFactory& GetInstance()
    {
        static ObjectFactory Instance;
        return Instance;
    }

    // A templated function to spawn any registered Object
    template <typename TObject>
    static Object* SpawnObject()
    {
        return new TObject();
    }

    // A factory function that spawns an object of the specified class name
    Object* SpawnObjectByName(const std::string& ClassName, const char* EditorName = "")
    {
        auto it = GetObjectRegistry().find(ClassName);
        if (it != GetObjectRegistry().end())
        {
            Object* obj = it->second();
            Objects.insert(std::make_pair(EditorName, obj));
            
            obj->Name = EditorName;
            obj->RegisterVariables();

            return obj;
        }
        return nullptr;
    }
    
    std::unordered_map<std::string, std::function<Object* ()>>& GetObjectRegistry() // Returns the Registry of class names
    {
        static std::unordered_map<std::string, std::function<Object* ()>> Registry;
        return Registry;
    }

    Object* FindObjectByName(std::string Name)
    {
        auto it = Objects.find(Name);
        if (it == Objects.end())
        {
            return nullptr;
        }
        return it->second;
    }

private:
    std::unordered_map<std::string, std::function<Object* ()>> ObjectRegistry;                      // Registry that maps class names to factory functions
};

Object.h class, the base for reflection

#pragma once

#include <map>
#include <string>
#include <typeinfo>
#include <vector>

// Holds variable type, value and name so it can be accessed
struct RegistryEntry
{
    const std::type_info* type;
    void* value;
    const char* name;
};

#define EditorVariableRegistry ObjectVariableRegistry::GetInstance().VariableRegistry 

class ObjectVariableRegistry
{
public:
    static ObjectVariableRegistry& GetInstance()
    {
        static ObjectVariableRegistry Instance;
        return Instance;
    }

    std::multimap<void*, RegistryEntry> VariableRegistry;

private:
    ObjectVariableRegistry() {}
    ObjectVariableRegistry(const ObjectVariableRegistry&) = delete;
};

class Object
{
protected:
    std::vector<RegistryEntry> EditorVariables;         // Container of all variables changable in Editor
    
public:
    #define SERIALIZE(var) EditorVariables.push_back(RegistryEntry{&typeid(var), &(var), #var}), true;

    void RegisterVariables()                            // Registers all editor variables into a global list. Does not work properly if placed in .cpp file
    {
        SERIALIZE(Name);
        
        for (auto& entry : EditorVariables)             // Loops through the EditorVariables and add them to the global VariableRegistry
        {
            EditorVariableRegistry.emplace(this, entry);
        }
    }
    
    std::string Name = "";
};

Foo.h. A simple class which inherits from Object, meaning it now has out-of-the box support for reflection.

#pragma once

#include "Factory.h"

SPAWNABLE(Foo);
class Foo : public Object
{
public: 
    Foo()
    {
        SERIALIZE(Bar);
    }
    
private:
    std::string Bar = "private variable";
};

Finally: Main.cpp. Note how we don't #include Foo.h anywhere in the program, and we can still inspect (and modify) Foo variables as long as we #include "Factory.h".

#include <iostream>

#include "Factory.h"
#include "Foo.h"

void PrintAllVariables()
{
    printf("Registry contents: (%zi) \n", EditorVariableRegistry.size()) ;
    for (const auto& var : EditorVariableRegistry)
    {
        if (Object* obj = static_cast<Object*>(var.first))
        {
            std::cout << "  Owning class name: " << obj->Name << "  Owning class instance address: " << var.first << std::endl;
        }

        std::cout << "  Variable name: " << var.second.name << "  Variable address: " << var.second.value << std::endl;
        std::cout << " Type: " << var.second.type->name() << std::endl;
        std::cout << "  Value: ";
        // Check the type of the variable
        if (*var.second.type == typeid(std::string))
        {
            std::cout << *static_cast<std::string*>(var.second.value);
        }
    }
}

int main()
{
    DoSpawn("Foo", "Dude");

    PrintAllVariables();

    return 0;
}

Output:

Registry contents: (2) 
  Owning class name: Dude  Owning class instance address: 0x561afd16cf80
  Variable name: Bar  Variable address: 0x561afd16cfb8
 Type: NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
  Value: private variable  Owning class name: Dude  Owning class instance address: 0x561afd16cf80
  Variable name: Name  Variable address: 0x561afd16cf98
 Type: NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
  Value: Dude




Aucun commentaire:

Enregistrer un commentaire