从运行时参数分派到不同的重载
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);
}
因为你的函数有相同的签名,你可以使用 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);
}
杠杆变量。
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
元组自动制作无序地图。
所有这一切只是为了避免两次提及支持的类型。
假设我有一组类型:
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);
}
因为你的函数有相同的签名,你可以使用 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);
}
杠杆变量。
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
元组自动制作无序地图。
所有这一切只是为了避免两次提及支持的类型。