从运行时参数分派到不同的重载

Dispatching from a runtime parameter to different overloads

假设我有一组类型:

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,double,string> my_types;
  static constexpr std::array<const char*,3> my_ids = {"int","double","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 仍然可以通过模板元编程技巧实现。

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

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

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

// 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, double, 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

因为你的函数有相同的签名,你可以使用 std::map 将 ids 映射到函数指针,例如:

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>},
  {"double", tag<double>},
  {"string", 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 元组自动制作无序地图。

所有这一切只是为了避免两次提及支持的类型。