5

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?

9
  • 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? Commented Sep 12, 2022 at 7:53
  • related stackoverflow.com/q/16268107/4117728 Commented Sep 12, 2022 at 7:55
  • also somewhat related stackoverflow.com/q/18942322/4117728 Commented Sep 12, 2022 at 7: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. Commented Sep 12, 2022 at 8:37
  • 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. Commented Sep 12, 2022 at 11:14

2 Answers 2

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...); } 
Sign up to request clarification or add additional context in comments.

5 Comments

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 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.
@Lyar Try this. This is just a random shot in the dark, I don't have the msvc you're using.
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 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.
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. 

1 Comment

This is exactly what I did before migrating the sln from VS2008 to VS2013.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.