简体   繁体   中英

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

I also have a print_result function that prints out their results (and the time they take to run, but trimmed here for simplicity):

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 (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.

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.

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. The function numbers are fixed, ie q1 maps to the constant 1 and that won't change at runtime.

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).
  2. Use std::function and std::bind to bind your functions you want to call to a functor object
  3. use the index into the array to call the 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:

3
3.5

See: Why does std::function can implicit convert to a std::function which has more parameter?

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:

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. Instead of using a had-made recursive structure, you could use a c++17 fold-expression:

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. 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

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.

You can create an object of the class as follows (unfortunately I don't see a way to avoid the std::make_pair() s)

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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