简体   繁体   English

通过指向 C++ 成员 function 的指针转发可变参数实例方法调用

[英]Forward a variadic instance method call via a pointer to member function in C++

I'm working on a class representation utility that would work in a similar way to Java's Class class. That is, a mechanism that would emulate class reflection.我正在研究 class 表示实用程序,它的工作方式与 Java 的Class class 类似。也就是说,一种可以模拟 class 反射的机制。

#include <map>
#include <stdexcept>
#include <string>

template<typename Class>
struct class_repr {

    std::map<std::string, uintptr_t> fields;
    std::map<std::string, void* (Class::*)(...)> methods;

    void declare_field(const std::string& name, void* pointer) {
        fields[name] = reinterpret_cast<uintptr_t>(pointer);
    }

    template<typename R, typename ...Params>
    void declare_instance_method(const std::string& name, R (Class::* pointer)(Params...)) {
        methods[name] = (void* (Class::*)(...)) pointer;
    }

    template<typename Tp>
    Tp& get_field(void* object, const std::string& name) {
        if (fields.count(name) == 0) throw std::invalid_argument("Field " + name + " not declared in the class descriptor");
        return *reinterpret_cast<Tp*>(uintptr_t(object) + fields.at(name));
    }

    template<typename R, typename ...Params>
    requires std::is_same_v<R, void>
    void invoke_instance_method(void* object, const std::string& name, Params&& ... params) {
        if (methods.count(name) == 0) throw std::invalid_argument("Method " + name + " not declared in the class descriptor");
        (reinterpret_cast<Class*>(object)->*methods.at(name))(std::forward<Params>(params)...);
    }

    template<typename R, typename ...Params>
    requires (not std::is_same_v<R, void>)
    R invoke_instance_method(void* object, const std::string& name, Params&& ... params) {
        if (methods.count(name) == 0) throw std::invalid_argument("Method " + name + " not declared in the class descriptor");
        return *static_cast<R*>((reinterpret_cast<Class*>(object)->*methods.at(name))(std::forward<Params>(params)...));
    }
};

And below is the class I'm testing it with:下面是我正在测试的 class:

#include <iostream>

class cat {

    std::string name, color;

    [[nodiscard]] const std::string& get_name() {
        return name;
    }

    [[nodiscard]] const std::string& get_color() {
        return color;
    }

    void say(std::string&& what) {
        std::cout << "[" << name << "]: " << what << std::endl;
    }

    void meow() {
        say("meow");
    }

    void say_color() {
        say("my fur is " + color);
    }

public:

    cat(std::string name, std::string color) : name(std::move(name)), color(std::move(color)) {}

    static class_repr<cat> get_representation() {
        class_repr<cat> descriptor;
        descriptor.declare_field("name", &(static_cast<cat*>(nullptr)->name));
        descriptor.declare_field("color", &(static_cast<cat*>(nullptr)->color));
        descriptor.declare_instance_method("get_name", &cat::get_name);
        descriptor.declare_instance_method("get_color", &cat::get_color);
        descriptor.declare_instance_method("say", &cat::say);
        descriptor.declare_instance_method("meow", &cat::meow);
        descriptor.declare_instance_method("say_color", &cat::say_color);
        return descriptor;
    }
};

This code works fine:此代码工作正常:

int main() {

    cat kitty("marble", "white");
    class_repr cat_class = cat::get_representation();

    cat_class.get_field<std::string>(&kitty, "name") = "skittle";
    cat_class.get_field<std::string>(&kitty, "color") = "gray";

    cat_class.invoke_instance_method<void>(&kitty, "meow");
    cat_class.invoke_instance_method<void>(&kitty, "say_color");
    std::cout << cat_class.invoke_instance_method<std::string>(&kitty, "get_name") << "'s color is indeed "
              << cat_class.invoke_instance_method<std::string>(&kitty, "get_color") << std::endl;

    return 0;
}

But when I try to call the say function, the code doesn't compile because non-primitive type objects cannot be passed through variadic method:但是当我尝试调用say function 时,代码无法编译,因为非原始类型对象无法通过可变参数方法传递:

cat_class.invoke_instance_method<void, std::string&&>(&kitty, "say", "purr"); // error

Is there any way around making this work as intended (so that it calls an equivalent of kitty.say("purr") )?有没有办法让这项工作按预期进行(这样它就相当于调用kitty.say("purr") )?

You can create a class representing any member function using type erasure (modified from this SO answer ) .您可以创建一个 class 代表任何成员 function 使用类型擦除(从这个 SO 答案修改) No void* , no C-stype ellipsis ... .没有void* ,没有 C 型省略号...

#include <memory>
#include <any>
#include <vector>
#include <functional>

class MemberFunction
{
public:

    template <typename R, typename C, typename... Args>
    MemberFunction(R (C::* memfunptr)(Args...))
    : type_erased_function{
        std::make_shared<Function<R, C, Args...>>(memfunptr)
    }
    {}

    template <typename R, typename C, typename... Args>
    R invoke(C* obj, Args&&... args){
        auto ret = type_erased_function->invoke(
            std::any(obj),
            std::vector<std::any>({std::forward<Args>(args)...})
        );
        if constexpr (!std::is_void_v<R>){
            return std::any_cast<R>(ret);
        }
    }

private:

    struct Concept {
        virtual ~Concept(){}
        virtual std::any invoke(std::any obj, std::vector<std::any> const& args) = 0;
    };

    template <typename R, typename C, typename... Args>
    class Function : public Concept
    {
    public:
        Function(R (C::* memfunptr)(Args...)) : func{memfunptr} {}

        std::any invoke(std::any obj, std::vector<std::any> const& args) override final
        {
            return invoke_impl(
                obj,
                args, 
                std::make_index_sequence<sizeof...(Args)>()
            );
        }
    private:

        template <size_t I>
        using Arg = std::tuple_element_t<I, std::tuple<Args...>>;

        template <size_t... I>
        std::any invoke_impl(std::any obj, std::vector<std::any> const& args, std::index_sequence<I...>)
        {
            auto invoke = [&]{ 
                return std::invoke(func, std::any_cast<C*>(obj), std::any_cast<std::remove_reference_t<Arg<I>>>(args[I])...); 
            };
            if constexpr (std::is_void_v<R>){
                invoke();
                return std::any();
            }
            else {
                return invoke();
            }
        }

        R (C::* func)(Args...);
    };

    std::shared_ptr<Concept> type_erased_function;

};

You store a std::map<std::string, MemberFunction> in your class_repr and change your declare_instance_method and invoke_instance_method like so:您将std::map<std::string, MemberFunction>存储在class_repr中,并像这样更改declare_instance_methodinvoke_instance_method

template<typename R, typename ...Params>
void declare_instance_method(const std::string& name, R (Class::* pointer)(Params...)) {
    methods.insert({name, MemberFunction(pointer)});
}

template<typename R, typename ...Params>
requires std::is_same_v<R, void>
void invoke_instance_method(Class* object, const std::string& name, Params&& ... params) {
    if (methods.count(name) == 0) throw std::invalid_argument("Method " + name + " not declared in the class descriptor");
    methods.at(name).invoke<void>(object, std::forward<Params>(params)...);
}

template<typename R, typename ...Params>
requires (not std::is_same_v<R, void>)
R invoke_instance_method(Class* object, const std::string& name, Params&& ... params) {
    if (methods.count(name) == 0) throw std::invalid_argument("Method " + name + " not declared in the class descriptor");
    return methods.at(name).invoke<R>(object, std::forward<Params>(params)...);
}

Live Demo现场演示

Note that this is a prototype.请注意,这是一个原型。 To make this generally applicable you still need to invest quite a bit of work: You have to consider const member functions and const arguments, member functions mutating inputs or returning references etc. Also note, that std::any stores by value, so you might create some unnecessary copies of the function arguments.为了使其普遍适用,您仍然需要投入大量工作:您必须考虑 const 成员函数和 const arguments,成员函数改变输入或返回引用等。另请注意, std::any按值存储,因此您可能会创建 function arguments 的一些不必要的副本。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM