std :: variant的参数归纳

时间:2019-12-24 07:14:02

标签: c++ templates c++17 generic-programming c++-standard-library

最近,我正在研究 ORM ,它通过执行以下操作来接受功能注册:

orm->register_func("NAME", &User::set_name);

因此,基本上,当数据库返回列NAME时, ORM 将使用set_name中的函数User。在开发过程中,我了解了更多关于std::variant的知识,这是我如何使用它的一个小例子:

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基本上是泛型函数指针(假设函数是类成员),而VARIANT_FPTR定义了可以在中注册的函数的可能性ORM

现在,由于std::variant类型,我遇到了问题。这是register_func的实现:

void register_func(std::string name, VARIANT_FPTR<T> ptr)
{
   _map.insert(std::make_pair(name, ptr));
}

但是,由于类似&User::set_name的参数不是std::variant类型,因此出现编译错误。

是否有一种方法可以使register_func诱导其参数类型? 如果没有,还有另一种方法可以进行类似的处理吗?

编译错误为:

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>’}

这是地图定义:

template<typename T>  class Orm
{
private:
   std::map<std::string, VARIANT_FPTR<T>> _map;
   // ...
};

3 个答案:

答案 0 :(得分:2)

您需要在std::map模板的实例化中提供确切的类型。例如,如果您需要User的成员函数,则需要以下内容:

std::map<std::string, VARIANT_FPTR<User>> _map;
//                                 ^^^^^

以下是一个完整的基本工作示例: 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;
}

答案 1 :(得分:1)

问题(假设编译器正确地说出有问题的成员函数是重载的)是std::variant<X,Y>并非 的构造函数接受X和{{ 1}}。相反,它具有构造器模板,该构造器执行重载解析以确定要构造的替代方案。该模板无法从重载集合的指针(或指向成员的指针)推断出简单的Y。如果只有一个重载适合任何变体的选择,则您可以自己提供这些重载:

T

如果仍然不清楚,可以使用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)); } 选择重载:

static_cast

答案 2 :(得分:0)

显然,在这种棘手的情况下,使用std :: variant进行参数诱导并不总是可行的。因此,为了解决这个问题,我最终决定使寄存器功能的使用更为冗长。在这里:

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));
}

要使用它,您需要编写:

orm->register_func<void, Class, const char *>("name", &Class::set_name);

有趣的事实

我发现某些情况下实际上可以引入某些参数。对于以下功能:

void Class::set_id(unsigned long id)

我显然可以像这样调用register_function:

orm->register_func("id", &Class::set_id);