C++ 模板避免长切换,同时调用具有不同 return 类型的函数
C++ templates to avoid long switches, while calling a function with different return types
我有很多功能 q1
、q2
、q3
等,每个功能都有不同的 return 类型(int
、int64_t
、std::string
、等等)。
我还有一个 print_result
函数,可以打印出他们的结果(以及他们花费 运行 的时间,但为了简单起见,这里进行了修剪):
template <typename T>
void print_result(T (*func)()) {
T res = func();
std::cout << res << std::endl;
}
我还有一个很大的 switch 语句来打印每个函数的结果:
switch (question_num) {
case 1: print_result(q1); break;
case 2: print_result(q2); break;
case 3: print_result(q3); break;
// ...
}
Objective: 我想用模板函数替换这个 switch 语句,以避免每次添加新函数时都复制每一行。
我曾尝试查看 C++ template instantiation: Avoiding long switches,但我是模板元编程的新手,所以不确定如何准确处理。
我当前未编译的尝试:
template <<int, typename> ...> struct FuncList {};
template <typename T>
bool handle_cases(int, T, FuncList<>) {
// default case
return false;
}
template <<int I, typename T> ...S>
bool handle_cases(int i, T (*func)(), FuncList<T, S...>) {
if (I != i) {
return handle_cases(i, func, FuncList<S...>());
}
print_result(func);
return true;
}
template <typename ...S>
bool handle_cases(int i, T (*func)()) {
return handle_cases(i, func, FuncList<S...>());
}
// ...
bool res = handle_cases<
<1, q1>, <2, q2>, <3, q3>
>(question_num);
// ...
我使用此模板的理想方式显示在最后一行。
请注意,这里提供了从函数编号到函数的映射。函数编号是固定的,即 q1
映射到常量 1
并且在 运行 时间不会改变。
编译错误(可能比较基础,但我对元编程了解不多):
error: expected unqualified-id before ‘<<’ token
17 | template <<int, typename> ...> struct FuncList {};
| ^~
我有一个不同的提议:
- 使用 std::array 而不是 switch(或者 std::map 如果 switch cases 是不连续的,std::array 有 O(1) 访问时间,std::map O (log(n)) 和开关 O(n).
- 使用 std::function 和 std::bind 将您要调用的函数绑定到函子对象
- 使用数组中的索引调用函数
- 如果需要传递额外数据,请使用占位符
#include <iostream>
#include <functional>
template <typename T>
void print_result(T (*func)()) {
T res = func();
std::cout << res << std::endl;
}
int int_function() {
return 3;
}
double double_function() {
return 3.5;
}
std::array<std::function<void()>, 2> functions({
std::bind(print_result<int>, int_function),
std::bind(print_result<double>, double_function),
});
int main() {
functions[0]();
functions[1]();
return 0;
}
输出:
3
3.5
参见:
更新:
带参数传递:
#include <iostream>
#include <functional>
template <typename T>
void print_result(T (*func)(int), int value) {
T res = func(value);
std::cout << res << std::endl;
}
int int_function(int value) {
return 3 * value;
}
double double_function(int value) {
return 3.5 * value;
}
std::array<std::function<void(int)>, 2> functions({
std::bind(print_result<int>, int_function, std::placeholders::_1),
std::bind(print_result<double>, double_function, std::placeholders::_1),
});
int main() {
functions[0](10);
functions[1](11);
return 0;
}
输出:
30
38.5
您可能会喜欢这样一个版本,它不需要任何类型的运行时容器,中间不生成任何对象,甚至不生成数据table,生成的代码非常少,也易于使用:
// Example functions
int fint() { return 1; }
double fdouble() { return 2.2; }
std::string fstring() { return "Hallo"; }
// your templated result printer
template < typename T>
void print_result( T parm )
{
std::cout << "The result of call is " << parm << std::endl;
}
// lets create a type which is able to hold functions
template < auto ... FUNCS >
struct FUNC_CONTAINER
{
static constexpr unsigned int size = sizeof...(FUNCS);
};
// and generate a interface to switch
template < unsigned int, typename T >
struct Switch_Impl;
template < unsigned int IDX, auto HEAD, auto ... TAIL >
struct Switch_Impl< IDX, FUNC_CONTAINER<HEAD, TAIL...>>
{
static void Do( unsigned int idx )
{
if ( idx == IDX )
{
// Your function goes here
print_result(HEAD());
}
else
{
if constexpr ( sizeof...(TAIL))
{
Switch_Impl< IDX+1, FUNC_CONTAINER<TAIL...>>::Do(idx);
}
}
}
};
// a simple forwarder to simplify the interface
template < typename T>
struct Switch
{
static void Do(unsigned int idx )
{
Switch_Impl< 0, T >::Do( idx );
}
};
// and lets execute the stuff
int main()
{
using FUNCS = FUNC_CONTAINER< fint, fdouble, fstring >;
for ( unsigned int idx = 0; idx< FUNCS::size; idx++ )
{
Switch<FUNCS>::Do(idx);
}
}
如果您可以使用 c++17,这是@Klaus 方法的“简化”版本。您可以使用 c++17 折叠表达式,而不是使用自制的递归结构:
template<auto... Funcs, std::size_t... I>
bool select_case(std::size_t i, std::integer_sequence<std::size_t, I...>) {
return ([&]{ if(i == I) { print_result(Funcs); return true; } return false; }() || ... );
}
template<auto... Funcs>
struct FuncSwitch {
static bool Call(std::size_t i) {
return select_case<Funcs...>(i, std::make_index_sequence<sizeof...(Funcs)>());
}
};
想法是将每个 Funcs
包装在一个 lambda 中,以便只调用与传递的索引对应的函数。请注意,折叠表达式中的 ||
短路。
可以这样使用:
float q0() { return 0.f; }
int q1() { return 1; }
std::string q2() { return "two"; }
int main() {
bool success = FuncSwitch<q0, q1, q2>::Call(1);
}
有关完整示例,请参阅 here。
鉴于你的“当前尝试”...在我看来你可以写一个 handle_cases
struct/class 几乎如下
struct handle_cases
{
std::map<int, std::function<void()>> m;
template <typename ... F>
handle_cases (std::pair<int, F> const & ... p)
: m{ {p.first, [=]{ print_result(p.second); } } ... }
{ }
void operator() (int i)
{ m[i](); }
};
在整数和使用函数调用 print_result
的 lambda 和调用请求的 lambda 的 operator()
之间的映射,给定相应的索引。
您可以如下创建 class 的对象(不幸的是,我没有看到避免 std::make_pair()
的方法)
handle_cases hc{ std::make_pair(10, q1),
std::make_pair(20, q2),
std::make_pair(30, q3),
std::make_pair(40, q4) };
并按如下方式使用
hc(30);
下面是一个完整的编译示例
#include <functional>
#include <map>
#include <iostream>
template <typename T>
void print_result (T(*func)())
{
T res = func();
std::cout << res << std::endl;
}
struct handle_cases
{
std::map<int, std::function<void()>> m;
template <typename ... F>
handle_cases (std::pair<int, F> const & ... p)
: m{ {p.first, [=]{ print_result(p.second); } } ... }
{ }
void operator() (int i)
{ m[i](); }
};
char q1 () { return '1'; }
int q2 () { return 2; }
long q3 () { return 3l; }
long long q4 () { return 4ll; }
int main ()
{
handle_cases hc{ std::make_pair(10, q1),
std::make_pair(20, q2),
std::make_pair(30, q3),
std::make_pair(40, q4) };
hc(30);
}
我有很多功能 q1
、q2
、q3
等,每个功能都有不同的 return 类型(int
、int64_t
、std::string
、等等)。
我还有一个 print_result
函数,可以打印出他们的结果(以及他们花费 运行 的时间,但为了简单起见,这里进行了修剪):
template <typename T>
void print_result(T (*func)()) {
T res = func();
std::cout << res << std::endl;
}
我还有一个很大的 switch 语句来打印每个函数的结果:
switch (question_num) {
case 1: print_result(q1); break;
case 2: print_result(q2); break;
case 3: print_result(q3); break;
// ...
}
Objective: 我想用模板函数替换这个 switch 语句,以避免每次添加新函数时都复制每一行。
我曾尝试查看 C++ template instantiation: Avoiding long switches,但我是模板元编程的新手,所以不确定如何准确处理。
我当前未编译的尝试:
template <<int, typename> ...> struct FuncList {};
template <typename T>
bool handle_cases(int, T, FuncList<>) {
// default case
return false;
}
template <<int I, typename T> ...S>
bool handle_cases(int i, T (*func)(), FuncList<T, S...>) {
if (I != i) {
return handle_cases(i, func, FuncList<S...>());
}
print_result(func);
return true;
}
template <typename ...S>
bool handle_cases(int i, T (*func)()) {
return handle_cases(i, func, FuncList<S...>());
}
// ...
bool res = handle_cases<
<1, q1>, <2, q2>, <3, q3>
>(question_num);
// ...
我使用此模板的理想方式显示在最后一行。
请注意,这里提供了从函数编号到函数的映射。函数编号是固定的,即 q1
映射到常量 1
并且在 运行 时间不会改变。
编译错误(可能比较基础,但我对元编程了解不多):
error: expected unqualified-id before ‘<<’ token
17 | template <<int, typename> ...> struct FuncList {};
| ^~
我有一个不同的提议:
- 使用 std::array 而不是 switch(或者 std::map 如果 switch cases 是不连续的,std::array 有 O(1) 访问时间,std::map O (log(n)) 和开关 O(n).
- 使用 std::function 和 std::bind 将您要调用的函数绑定到函子对象
- 使用数组中的索引调用函数
- 如果需要传递额外数据,请使用占位符
#include <iostream>
#include <functional>
template <typename T>
void print_result(T (*func)()) {
T res = func();
std::cout << res << std::endl;
}
int int_function() {
return 3;
}
double double_function() {
return 3.5;
}
std::array<std::function<void()>, 2> functions({
std::bind(print_result<int>, int_function),
std::bind(print_result<double>, double_function),
});
int main() {
functions[0]();
functions[1]();
return 0;
}
输出:
3
3.5
参见:
更新:
带参数传递:
#include <iostream>
#include <functional>
template <typename T>
void print_result(T (*func)(int), int value) {
T res = func(value);
std::cout << res << std::endl;
}
int int_function(int value) {
return 3 * value;
}
double double_function(int value) {
return 3.5 * value;
}
std::array<std::function<void(int)>, 2> functions({
std::bind(print_result<int>, int_function, std::placeholders::_1),
std::bind(print_result<double>, double_function, std::placeholders::_1),
});
int main() {
functions[0](10);
functions[1](11);
return 0;
}
输出:
30
38.5
您可能会喜欢这样一个版本,它不需要任何类型的运行时容器,中间不生成任何对象,甚至不生成数据table,生成的代码非常少,也易于使用:
// Example functions
int fint() { return 1; }
double fdouble() { return 2.2; }
std::string fstring() { return "Hallo"; }
// your templated result printer
template < typename T>
void print_result( T parm )
{
std::cout << "The result of call is " << parm << std::endl;
}
// lets create a type which is able to hold functions
template < auto ... FUNCS >
struct FUNC_CONTAINER
{
static constexpr unsigned int size = sizeof...(FUNCS);
};
// and generate a interface to switch
template < unsigned int, typename T >
struct Switch_Impl;
template < unsigned int IDX, auto HEAD, auto ... TAIL >
struct Switch_Impl< IDX, FUNC_CONTAINER<HEAD, TAIL...>>
{
static void Do( unsigned int idx )
{
if ( idx == IDX )
{
// Your function goes here
print_result(HEAD());
}
else
{
if constexpr ( sizeof...(TAIL))
{
Switch_Impl< IDX+1, FUNC_CONTAINER<TAIL...>>::Do(idx);
}
}
}
};
// a simple forwarder to simplify the interface
template < typename T>
struct Switch
{
static void Do(unsigned int idx )
{
Switch_Impl< 0, T >::Do( idx );
}
};
// and lets execute the stuff
int main()
{
using FUNCS = FUNC_CONTAINER< fint, fdouble, fstring >;
for ( unsigned int idx = 0; idx< FUNCS::size; idx++ )
{
Switch<FUNCS>::Do(idx);
}
}
如果您可以使用 c++17,这是@Klaus 方法的“简化”版本。您可以使用 c++17 折叠表达式,而不是使用自制的递归结构:
template<auto... Funcs, std::size_t... I>
bool select_case(std::size_t i, std::integer_sequence<std::size_t, I...>) {
return ([&]{ if(i == I) { print_result(Funcs); return true; } return false; }() || ... );
}
template<auto... Funcs>
struct FuncSwitch {
static bool Call(std::size_t i) {
return select_case<Funcs...>(i, std::make_index_sequence<sizeof...(Funcs)>());
}
};
想法是将每个 Funcs
包装在一个 lambda 中,以便只调用与传递的索引对应的函数。请注意,折叠表达式中的 ||
短路。
可以这样使用:
float q0() { return 0.f; }
int q1() { return 1; }
std::string q2() { return "two"; }
int main() {
bool success = FuncSwitch<q0, q1, q2>::Call(1);
}
有关完整示例,请参阅 here。
鉴于你的“当前尝试”...在我看来你可以写一个 handle_cases
struct/class 几乎如下
struct handle_cases
{
std::map<int, std::function<void()>> m;
template <typename ... F>
handle_cases (std::pair<int, F> const & ... p)
: m{ {p.first, [=]{ print_result(p.second); } } ... }
{ }
void operator() (int i)
{ m[i](); }
};
在整数和使用函数调用 print_result
的 lambda 和调用请求的 lambda 的 operator()
之间的映射,给定相应的索引。
您可以如下创建 class 的对象(不幸的是,我没有看到避免 std::make_pair()
的方法)
handle_cases hc{ std::make_pair(10, q1),
std::make_pair(20, q2),
std::make_pair(30, q3),
std::make_pair(40, q4) };
并按如下方式使用
hc(30);
下面是一个完整的编译示例
#include <functional>
#include <map>
#include <iostream>
template <typename T>
void print_result (T(*func)())
{
T res = func();
std::cout << res << std::endl;
}
struct handle_cases
{
std::map<int, std::function<void()>> m;
template <typename ... F>
handle_cases (std::pair<int, F> const & ... p)
: m{ {p.first, [=]{ print_result(p.second); } } ... }
{ }
void operator() (int i)
{ m[i](); }
};
char q1 () { return '1'; }
int q2 () { return 2; }
long q3 () { return 3l; }
long long q4 () { return 4ll; }
int main ()
{
handle_cases hc{ std::make_pair(10, q1),
std::make_pair(20, q2),
std::make_pair(30, q3),
std::make_pair(40, q4) };
hc(30);
}