【问题标题】:Parameter induction with std::variant使用 std::variant 进行参数归纳
【发布时间】:2019-12-24 07:14:02
【问题描述】:

最近我正在开发一个ORM,它通过执行以下操作来接受函数的注册:

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

所以基本上当数据库返回列NAME 时,ORM 将使用User 中的函数set_name。在开发过程中,我了解了更多关于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));
}

但是,我得到一个编译错误,因为像 &amp;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;
   // ...
};

【问题讨论】:

  • 你能举一个例子来说明你是如何调用 register_func 的,你得到的确切错误是什么?您可能需要像这样显式构造变体:VARIANT_FPTR&lt;T&gt;(&amp;User::set_name)
  • 我完全按照我在第一段代码中显示的方式调用 register_func 并编辑帖子以显示错误。
  • @ClémentJean 然后你可以编写一个帮助模板函数来从类中获取正确的重载。像这样:godbolt.org/z/4T3T4g。这是没有测试的示例代码:godbolt.org/z/-MArdf。如果这解决了问题,我可以用它来更新答案!
  • 只是为了确认错误信息的意义,User::set_name超载了吧?
  • @ClémentJean:不——我说的是“过载”,就像 GCC 所做的那样。

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


【解决方案1】:

您需要在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;
}

【讨论】:

  • 地图实际上是在一个提供这个T的泛型类中,所以我还需要这样做吗?
【解决方案2】:

问题(假设编译器正确地说有问题的成员函数已重载)是std::variant&lt;X,Y&gt; 确实 有接受XY 的构造函数。相反,它有一个构造函数 template 执行重载决议以确定构造哪个替代方案。该模板无法从指向重载集的未知成员的指针(或指向成员的指针)推导出简单的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选择重载:

orm->register_func
  ("NAME",
   static_cast<FPTR<void, T, const char *>>
     (&User::set_name));

【讨论】:

    【解决方案3】:

    显然,在这种棘手的情况下,使用 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);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-01-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多