从运行时参数调度到不同的重载

问题描述

假设我有一组类型:

constexpr std::tuple<int,double,string> my_types;

一组用于识别它们的值:

constexpr std::array<const char*,3> my_ids = {"int","double","string"}; // const char* instead of string to be constexpr-compatible

一个重载集

template<class T> bool my_fun(my_type complex_object) { /* some treatment depending on type T */ }

我有一个这样的手动调度功能

string my_disp_fun(my_type complex_object) {
  const char* id = get_info(complex_object);
  using namespace std::string_literals;
  if (id == "int"s) {
    return my_fun<int>(complex_object);
  } else if (id == "double"s) {
    return my_fun<double>(complex_object);
  } else if (id == "string"s) {
    return my_fun<string>(complex_object);
  } else {
    throw;
  }
}

因为我看到这种模式一次又一次地出现,每次都有不同的 my_fun,我想用类似的东西替换它:

struct my_mapping {
  static constexpr std::tuple<int,string> my_types;
  static constexpr std::array<const char*,"string"}; // const char* instead of string to be constexpr-compatible
}

string my_disp_fun(my_type complex_object) {
  const char* id = get_info(complex_object);
  return
    dispatch<my_mapping>(
      id,my_fun // pseudo-code since my_fun is a template
    );
}

如何实现调度功能?我非常有信心它可以做到,但到目前为止,我想不出一个相当不错的 API,它仍然可以通过模板元编程技巧来实现。

我相信人们已经需要解决这种问题了。这个图案有名字吗?我真的不知道如何用简洁的技术术语来限定它...

附带问题:是否与模式匹配提议有关?我不确定,因为论文似乎对匹配部分更感兴趣,而不是从中生成分支,对吧?

解决方法

由于您的函数具有相同的签名,您可以使用 std::map 将 id 映射到函数指针,例如:

template<class T>
std::string my_fun(my_type complex_object)
{
    /* some treatment depending on type T */ 
    return ...;
}

using my_func_type = std::string(*)(my_type);

const std::map<std::string,my_func_type> my_funcs = {
    {"int",&my_fun<int>},{"double",&my_fun<double>},{"string",&my_fun<std::string>}
};

std::string my_disp_fun(my_type complex_object)
{
    const char *id = get_info(complex_object);
    auto iter = my_funcs.find(id);
    if (iter == my_funcs.end())
        throw ...;
    return iter->second(complex_object);
}

Demo

,

利用变体。

template<class T>struct tag_t{using type=T};
template<class T>constexpr tag_t<T> tag={};
template<class...Ts>
using tag_enum = std::variant<tag_t<Ts>...>;

现在 tag_enum 是一种在运行时将类型存储为值的类型。它的运行时表示是一个整数 (!),但 C++ 知道整数表示特定类型。

我们现在只需要将您的字符串映射到整数

using supported_types=tag_enum<int,double,std::string>;

std::unordered_map<std::string,supported_types> name_type_map={
  {"int",tag<int>},tag<double>},tag<std::string>},};

如果需要,这个映射可以从一个数组和一个元组构建,或者在某处全局化,或者做成一个函数。

重点是,任何类型到 tag_enum 的映射都可用于自动调度函数。

查看方法:

string my_disp_fun(my_type complex_object) {
  const char* id = get_info(complex_object);
  return std::visit( [&](auto tag){
    return my_fun<typename decltype(tag)::type>( complex_object );
  },name_type_map[id] };
}

重构它以处理您想要的任何级别的自动化应该很容易。

如果您采用将 T 作为 tag_t 作为第一个参数传递的约定,那么重构会变得更加容易。

#define RETURNS(...)\
  noexcept(noexcept(__VA_ARGS__)) \
  -> decltype(__VA_ARGS__) \
  { return __VA_ARGS__; }

#define MAKE_CALLER_OF(...) \
  [](auto&&...args) \
  RETURNS( (__VA_ARGS__)(decltype(args)(args)...) )

现在您可以轻松地将模板函数包装到对象中

template<class F>
auto my_disp_fun(my_type complex_object F f) {
  const char* id = get_info(complex_object);
  return std::visit( [&](auto tag){
    return f( tag,complex_object );
  },name_type_map[id] }; // todo: handle failure to find it
}

然后

std::string s = my_disp_fun(obj,MAKE_CALLER_OF(my_fun));

为您发货。

(理论上我们可以在宏中传递模板参数,但上面的宏是通用的,而进行奇怪标签解包的宏则没有)。

我们也可以制作一个全局类型映射。

template<class T>
using type_entry = std::pair<std::string,tag_t<T>>;
#define TYPE_ENTRY_EX(NAME,X) type_entry<X>{ NAME,tag<X> }
#define TYPE_ENTRY(X) TYPE_ENTRY_EX(#X,X)

auto TypeTable = std::make_tuple(
  TYPE_ENTRY(int),TYPE_ENTRY(double),TYPE_ENTRY_EX("string",std::string)
);

template<class Table>
struct get_supported_types_helper;
template<class...Ts>
struct get_supported_types_helper<std::tuple<type_entry<Ts>...>> {
  using type = tag_enum<Ts...>;
};
template<class Table>
using get_supported_types = typename get_supported_types_helper<Table>::type;

从那里你可以做一些事情,比如自动从 TypeTable 元组制作无序映射。

所有这些只是为了避免不得不提及支持的类型两次。

,

我不确定这是否是您要查找的内容。但是你可以在不需要持有额外的类型数组的情况下做到这一点:

// overload visitor trick
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
// deduction guide
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

int main() {
    std::tuple<int,const char*> tup = {10,2.5,"hello"};
    auto f = overloaded {
        [](int arg){std::cout << arg << " + 3 = " << arg + 3 << std::endl;},[](double arg){std::cout << arg << " * 2 = " << arg * 2 << std::endl;},[](const std::string& arg){std::cout << "string: " << arg << std::endl;}
    };
    std::apply([&](const auto&... e){ (f(e),...);},tup);
}

代码:http://coliru.stacked-crooked.com/a/3bfdd35f89ceeff9