简体   繁体   English

C++ 模板以避免长开关,同时调用具有不同返回类型的 function

[英]C++ templates to avoid long switches, while calling a function with different return types

I have many functions q1 , q2 , q3 , etc., each with a different return type ( int , int64_t , std::string , etc.).我有许多函数q1q2q3等,每个函数都有不同的返回类型( intint64_tstd::string等)。

I also have a print_result function that prints out their results (and the time they take to run, but trimmed here for simplicity):我也有一个print_result function 打印出他们的结果(以及他们运行的时间,但为了简单起见在这里修剪):

template <typename T>
void print_result(T (*func)()) {
  T res = func();
  std::cout << res << std::endl;
}

I also have big switch statement to print the result for each of the functions:我也有很大的 switch 语句来打印每个函数的结果:

switch (question_num) {
  case 1: print_result(q1); break;
  case 2: print_result(q2); break;
  case 3: print_result(q3); break;
  // ...
}

Objective: I would like to replace this switch statement with a template function, to avoid copying each line every time I add a new function.目标:我想用模板 function 替换这个 switch 语句,以避免每次添加新的 function 时复制每一行。

I have tried to look at C++ template instantiation: Avoiding long switches , but I'm new to template metaprogramming, so not sure how to handle this exactly.我试图查看C++ 模板实例化:避免长开关,但我是模板元编程的新手,所以不知道如何准确处理这个问题。

My current attempt that doesn't compile:我目前无法编译的尝试:


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);
// ...

My ideal way of using this template is shown at the last line there.我使用这个模板的理想方式显示在最后一行。

Note that the mappings from the function number to the function is provided there.请注意,此处提供了从 function 编号到 function 的映射。 The function numbers are fixed, ie q1 maps to the constant 1 and that won't change at runtime. function 数字是固定的,即q1映射到常数1 ,并且在运行时不会改变。

The compilation error (it might be rather basic but I really don't know much about metaprogramming):编译错误(它可能相当基本,但我真的不太了解元编程):

error: expected unqualified-id before ‘<<’ token
   17 | template <<int, typename> ...> struct FuncList {};
      |          ^~

I've got a different proposal:我有一个不同的建议:

  1. Use an std::array instead of switch (or std::map if the switch cases are non-continuous, std::array has O(1) access time, std::map O(log(n)) and switch O(n).使用 std::array 代替开关(或 std::map 如果开关情况不连续,std::array 具有 O(1) 访问时间,std::map O(log(n)) 和开关 O (n)。
  2. Use std::function and std::bind to bind your functions you want to call to a functor object使用 std::function 和 std::bind 将要调用的函数绑定到仿函数 object
  3. use the index into the array to call the function使用数组中的索引来调用 function
  4. Use placeholders if you need to pass additional data如果您需要传递其他数据,请使用占位符
#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;
}

Output: Output:

3
3.5

See: Why does std::function can implicit convert to a std::function which has more parameter?请参阅: 为什么 std::function 可以隐式转换为具有更多参数的 std::function?

Update:更新:

With parameter passing:通过参数传递:

#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;
}

Output: Output:

30
38.5

You may like a version which do not need any kind of runtime containers, did not generate any objects in between and even do not generate a data table and generates very less code and is also easy to use:您可能喜欢不需要任何类型的运行时容器、中间不生成任何对象、甚至不生成数据表、生成的代码非常少且易于使用的版本:

// 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);
    }
}
                                                                                                                                                                  

If you can use c++17, here's a "simplified" version of @Klaus's approach.如果您可以使用 c++17,这是@Klaus 方法的“简化”版本。 Instead of using a had-made recursive structure, you could use a c++17 fold-expression:您可以使用 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)>());
    }
};

The idea is to wrap each of Funcs in a lambda such that only the function corresponding to the index passed is called.想法是将每个Funcs包装在 lambda 中,以便仅调用与传递的索引相对应的 function。 Note that the ||注意|| in the fold expression short-circuits.在折叠表达式短路。 Would be used like this:会这样使用:

float q0() { return 0.f; }
int q1() { return 1; }
std::string q2() { return "two"; }


int main() {

    bool success = FuncSwitch<q0, q1, q2>::Call(1);
}

See here for a complete example.有关完整示例,请参见此处

Given you "current attempt"... it seems to me that you could write a handle_cases struct/class almost as follows鉴于您“当前的尝试”......在我看来,您几乎可以编写一个handle_cases结构/类,如下所示

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](); }
 };

with a map between an integer and a lambda that call print_result with the function and an operator() that call the requested lambda, given the corresponding index. with a map between an integer and a lambda that call print_result with the function and an operator() that call the requested lambda, given the corresponding index.

You can create an object of the class as follows (unfortunately I don't see a way to avoid the std::make_pair() s)您可以按如下方式创建 class 的 object (不幸的是,我看不到避免使用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) };

and using it as follows并按如下方式使用它

hc(30);

The following is a full compiling example下面是一个完整的编译示例

#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);
 }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM