问题
Recenty I am working on an ORM which accepts registration of functions by doing the following:
orm->register_func("NAME", &User::set_name);
So basically when the database returns the column NAME
, the ORM will use the function set_name
in User
. During the development I learned more about std::variant
and here is a little example of how I use it:
template<typename RET, typename T, typename ...Args>
using FPTR = RET(T::*)(Args...);
template<typename T>
using VARIANT_FPTR = std::variant<FPTR<void, T, const char *>, FPTR<void, T, const unsigned int>, long int>;
FPTR
is basically a generic function pointer (assuming that the function is a class member) and VARIANT_FPTR
defines the possibilities of functions I can register in the ORM.
Now, I have a problem due to the std::variant
type. Here is the register_func
implementation:
void register_func(std::string name, VARIANT_FPTR<T> ptr)
{
_map.insert(std::make_pair(name, ptr));
}
However, I get a compilation error since a parameter like &User::set_name
is not of type std::variant
.
Is there a way to make the register_func
induce the type of its parameter?
If not is there another way to make a similar process?
The compilation error is:
error: cannot convert ‘<unresolved overloaded function type>’ to ‘VARIANT_FPTR<services::User>’ {aka ‘std::variant<void (services::User::*)(const char*), void (services::User::*)(unsigned int), long int>’}
Here is the map definition:
template<typename T> class Orm
{
private:
std::map<std::string, VARIANT_FPTR<T>> _map;
// ...
};
回答1:
You need to provide the exact type in the instantiation of the std::map
template. For instance, if you need User
's member functions in it, you need the following:
std::map<std::string, VARIANT_FPTR<User>> _map;
// ^^^^^
Following is a complete minimal working example: See live online
#include <iostream>
#include <variant>
#include <string>
#include <map>
struct User
{
template<typename T>
void set_name(T)
{}
};
template<typename RET, typename T, typename ...Args>
using FPTR = RET(T::*)(Args...);
template<typename T>
using VARIANT_FPTR = std::variant<FPTR<void, T, const char*>, FPTR<void, T, const unsigned int>, long int>;
template<typename T> class Orm
{
private:
std::map<std::string, VARIANT_FPTR<T>> _map;
public:
void register_func(std::string name, VARIANT_FPTR<User> ptr)
{
_map.emplace(name, ptr);
}
};
int main()
{
Orm<User> orm{};
orm.register_func("NAME", &User::set_name<const char*>);
orm.register_func("NAME", &User::set_name<const unsigned int>);
return 0;
}
回答2:
The issue (assuming that the compiler is correct in saying that the member function in question is overloaded) is that std::variant<X,Y>
does not have constructors accepting X
and Y
. Instead, it has a constructor template that performs overload resolution to determine which alternative to construct. That template cannot deduce a simple T
from a pointer (or pointer-to-member) to an unknown member of an an overload set. In the case where only one of the overloads is suitable for any of the variant’s choices, you can simply provide those overloads yourself:
template<class T>
auto pick(FPTR<void, T, const char *> f) {return f;}
auto pick(FPTR<void, T, const unsigned int> f) {return f;}
/* elsewhere */ {
orm->register_func("NAME", pick(&User::set_name));
}
If that’s still ambiguous, you can use static_cast
to choose the overload:
orm->register_func
("NAME",
static_cast<FPTR<void, T, const char *>>
(&User::set_name));
回答3:
Apparently parameter induction with std::variant is not always feasible in this kind of tricky case. So to solve this problem I finally decided to make the usage of register function a little bit more verbose. Here it is:
template<typename RES, typename CLASS, typename ...Args>
void register_func(std::string name, FPTR<RES, CLASS, Args...> ptr) {
_map.insert(std::make_pair(name, ptr));
}
thus to use it you need to write:
orm->register_func<void, Class, const char *>("name", &Class::set_name);
Interesting fact
I found out that some of the parameter can actually be inducted in some cases. In the case of the following function:
void Class::set_id(unsigned long id)
I apparently can just call the register_function like this:
orm->register_func("id", &Class::set_id);
来源:https://stackoverflow.com/questions/59465171/parameter-induction-with-stdvariant