6

I'm working on creating a simple reflector in C++11, it stores function pointers of instances functions as:

static std::unordered_map<std::string, std::pair<void(EmptyClass::*)(void), int>>* methods;
template<typename ClassType, typename returnType, typename... Args>
static void RegistFunction(std::string name, returnType(ClassType::* func)(Args... args)) {
    (*methods)[name] = std::make_pair((void(EmptyClass::*)())func, sizeof...(Args));
}

template<typename ReturnType, typename ClassType, typename... Args>
static ReturnType ExecuteFunction(ClassType* object, std::string name, Args... args) {
    if (object == NULL) return;
    ReturnType(ClassType:: * func)(Args...) = (ReturnType(ClassType::*)(Args...))(*methods)[name].first;
    return (object->*func)(std::forward<Args>(args)...);
}

But when I want to call ExecuteFunction, the number of arguments may be more than the number that function pointer actually accepts. So I need to remove some arguments from the tail of argument list, but it seems I can only remove from head.

template<typename ReturnType, typename ClassType, typename Arg, typename... Args>
    static ReturnType ExecuteFunction(ClassType* object, std::string name, Arg arg, Args... args) {
        if (sizeof...(Args) + 1 > (*methods)[name].second) {
            return ExecuteFunction<ReturnType>(std::forward<ClassType*>(object), std::forward<std::string>(name), std::forward<Args>(args)...);
        }
        if (object == NULL) return;
        ReturnType(ClassType:: * func)( Arg, Args...) = (ReturnType(ClassType::*)(Arg, Args...))(*methods)[name].first;
        return (object->*func)(std::forward<Arg>(arg), std::forward<Args>(args)...);
    }

Is there any solution to remove arguments at the tail of variadic method template?

Lyar
  • 63
  • 3
  • Is it ok for you to collect the arguments in a tuple (not on the public interface, internally), or are you against that kind of move/copy? – lorro Sep 12 '22 at 07:53
  • related https://stackoverflow.com/q/16268107/4117728 – 463035818_is_not_an_ai Sep 12 '22 at 07:55
  • also somewhat related https://stackoverflow.com/q/18942322/4117728 – 463035818_is_not_an_ai Sep 12 '22 at 07:58
  • Oops. Thanks for your comment. But I'm actually migrating a project of VS2008 to VS2022 (using compiler of VS2013), some of the lib files are complied with VS2008 tool chain and I cannot access their source. So the only C++11 features I can use are "auto", variadic template and other things requires no header files fromlater version's tool chain. "std::tuple" in this case cannot be included. – Lyar Sep 12 '22 at 08:37
  • sometimes `std::tuple` is only used to have a type that acts as a list of types. You do not need `std::tuple` for such uses, but `template struct my_types{};` is sometimes completely sufficient (and necessary helpers are also not too difficult to reimplement). – 463035818_is_not_an_ai Sep 12 '22 at 08:51
  • I tried to implement in this way, but how can I pass the type list and the actual argument list to my function pointer? – Lyar Sep 12 '22 at 11:03
  • Notice that you also have to handle less argument (your `if` isn't `if constexpr`, the branch has also to be instantiated). – Jarod42 Sep 12 '22 at 11:05
  • 1
    I'm afraid your task will be obscenely difficult. `std::tuple` is in the standard library for a reason. On the other hand, if you don't insist on being fully generic, you can enumerate all possible functions and switch on them. – Passer By Sep 12 '22 at 11:14
  • Notice that the cast is fragile: reference mismatch, conversions (`float`<-> `int`, `const char*`->`std::string`, ...). leading easily to UB. – Jarod42 Sep 12 '22 at 11:18

2 Answers2

1

Here's a C++11 implementation depending only on std::string and std::unordered_map. Some mandatory remarks:

  • As mentioned, this is extremely brittle due to inferring the function type by the provided arguments. This is UB waiting to happen.
  • method really shouldn't be a pointer.
  • If your return type is not assignable, this will break spectacularly.
  • The class pointer really should be a reference instead.
  • If you think the implementation is insane, then yes, it is indeed, and you should give up on being fully generic.

A C++11 implementation of std::index_sequence and friends can be found here.

See it in action.

template<typename...>
struct typelist {};

template<size_t, typename, typename, typename, typename>
struct call;

template<size_t N, typename R, typename C, typename... Accum, typename Head, typename... Tail>
struct call<N, R, C, typelist<Accum...>, typelist<Head, Tail...>>
    : call<N, R, C, typelist<Accum..., Head>, typelist<Tail...>>
{
};

template<typename R, typename C, typename... Accum, typename Head, typename... Tail>
struct call<sizeof...(Accum), R, C, typelist<Accum...>, typelist<Head, Tail...>>
{
    template<typename... Ts>
    int operator()(Ts&&...)
    {
        return 0;
    }

    template<typename... Ts>
    int operator()(R& ret, void (EmptyClass::* g)(), C& obj, Accum&... args, Ts&&...)
    {
        auto f = (R (C::*)(Accum...))g;
        ret = (obj.*f)(std::move(args)...);
        return 0;
    }
};

template<typename R, typename C, typename... Args, size_t... Is>
R switcher(int i, index_sequence<Is...>, void (EmptyClass::* g)(), C& obj, Args&... args)
{
    R ret{};
    int unused[] = {(i == Is ?
                   call<Is, R, C, typelist<>, typelist<Args..., void>>{}(ret, g, obj, args...)
                   : 0)...};

    (void)unused;
    return ret;
}

template<typename C, typename R, typename... Args>
void reg(std::string name, R (C::* func)(Args... args)) {
    (*methods)[name] = std::make_pair((void (EmptyClass::*)())func, sizeof...(Args));
}

template<typename R, typename C, typename... Args>
R exec(C* obj, std::string name, Args... args) {
    if(obj == nullptr)
        throw "a tantrum";
    
    auto& info = (*methods)[name];
    auto g = info.first;
    size_t i = info.second;
    if(i > sizeof...(Args))
        throw "a fit";

    return switcher<R>(i, make_index_sequence<sizeof...(Args) + 1>{}, g, *obj, args...);
}
Passer By
  • 19,325
  • 6
  • 49
  • 96
  • It seems that the msvc I'm using does not accept sizeof...(Accum) as a specilized template's size_t argument. Is there any way of work around? – Lyar Sep 12 '22 at 15:55
  • @Lyar I don't know, depends on what bug msvc has. If it chokes on non-type template parameters specializations in general, then I don't think anything can be done. – Passer By Sep 12 '22 at 16:03
  • 1
    @Lyar Try [this](https://godbolt.org/z/7W6Wf3Tbd). This is just a random shot in the dark, I don't have the msvc you're using. – Passer By Sep 12 '22 at 16:13
  • Exactly. MSVC says non-type parameter of a partial specialization must be a simple identifier. Fortunately in my usecase all the functions are with 6 or less parameters, it's not difficult to enumerate all possible argument numbers. In addition, why the container of reflected functions should not be a pointer? And why the instance parameter should be reference instead of pointer? – Lyar Sep 12 '22 at 16:17
  • @Lyar Then the random attempt very well might work. You should avoid pointers usually unless you have good reasons not to. `methods` is likely going to suffer many lifetime problems. A pointer argument that can only legally be non-null should just be a reference instead, and let the callee decide how to deal with null pointers. – Passer By Sep 12 '22 at 16:21
0

To continue the old-way, you might do it pre-C++11 with hard-coded limit:

// No args
template<typename ReturnType, typename ClassType>
static ReturnType ExecuteFunction(ClassType* object, std::string name)
{
    switch ((*methods)[name].second) {
        case 0: {
            if (object == NULL) return {};
            auto func = (ReturnType(ClassType::*)())((*methods)[name].first);
            return (object->*func)();
        }
        default: throw std::runtime_error("Wrong argument");
    }
}

// One arg
template<typename ReturnType, typename ClassType, typename T1>
static ReturnType ExecuteFunction(ClassType* object, std::string name, T1 arg1)
{
    switch ((*methods)[name].second) {
        case 0: return ExecuteFunction(object, name);
        case 1: {
            if (object == NULL) return {};
            auto func = (ReturnType(ClassType::*)(T1))((*methods)[name].first);
            return (object->*func)(std::forward<T1>(arg1));
        }
        default: throw std::runtime_error("Wrong argument");
    }
}

// Two args
template<typename ReturnType, typename ClassType, typename T1, typename T2>
static ReturnType ExecuteFunction(ClassType* object, std::string name, T1 arg1, T2 arg2)
{
    switch ((*methods)[name].second) {
        case 0: return ExecuteFunction(object, name);
        case 1: return ExecuteFunction(object, name, std::forward<T1>(arg1));
        case 2: {
            if (object == NULL) return {};
            auto func = (ReturnType(ClassType::*)(T1, T2))((*methods)[name].first);
            return (object->*func)(std::forward<T1>(arg1), std::forward<T2>(arg2));
        }
        default: throw std::runtime_error("Wrong argument");
    }
}

// .. up to reasonable limit.
Jarod42
  • 203,559
  • 14
  • 181
  • 302