Avoid the use of if and hardcoded string

1

There is some way to avoid the use of if and the harcoded string in the redirect function, perhaps using templates (metaprogramming). The idea is to receive a string and call a function.

#include <string>
#include <iostream>

void account()
{
    std::cout << "accout method" << std::endl;
}

void status()
{
    std::cout << "status method" << std::endl;
}

void redirect(std::string method_to_call)
{
    if(method_to_call == "account")
    {
        account();
    }
    else if(method_to_call == "status")
    {
        status();
    }
    else
    {
        std::cout << "method not found!!" << std::endl;
    }
}

int main()
{
    std::string method_name;
    std::cin >> method_name;

    redirect(method_name);

    return 0;
}
    
asked by jsubi 09.09.2017 в 11:30
source

4 answers

1

The trick is to search, within the chains you want to check, a unique feature that allows you to distinguish between them without having to compare them 1 to 1.

In the simple example that you expose, with only 2 strings, you have 4 valid methods:

  • For the content of a certain position (in your example, the initial letter).

  • By the length.

  • Searching in an auxiliary list.

  • Calculating a signature of the string.

  • The first 2 are the easiest to implement; unfortunately, they require validity previously the string; that is, they do not check all the string, only certain properties. Method 1 would confuse between "hola" e "hijo" (both start with 'h' ). Method 2 would make the same mistake: both are 4 characters.

    The 3 performs the check itself. Actually, it is a literal chaining . Check the chains 1 to 1, automatically. And it is prone to errors.

    The 4 is much more generic, and gives rise to automate the process, through additional structures, such as std:map . And avoid errors, by being able to attach additional information to the chains.

    I give you an example of each method; may have some error (I have not checked), but illustrate the different methods exposed:

    For the content of a certain position.

    void redirect( const std::string &method_to_call ) {
      switch( method_to_call[0] ) {
      case 'a':
        account( );
        break;
    
      case 's':
        status( );
        break;
    
      default:
        std::cout << "method not found!!" << std::endl;
      }
    }
    

    By the length.

    void redirect( const std::string &method_to_call ) {
      switch( method_to_call.size( ) ) {
      case 7:
        account( );
        break;
    
      case 6:
        status( );
        break;
    
      default:
        std::cout << "method not found!!" << std::endl;
      }
    }
    

    Searching a list.

    ::std::list< ::std::string > KeywordsList;
    KeywordsList.push_front( "accout" );
    KeywordsList.push_front( "status" );
    
    ...
    
    void redirect( const std::string &method_to_call ) {
      auto iter = find( KeywordsList.begin( ),
                        KeywordsList.end( ),
                        method_to_call );
    
      switch( distance( iter, KeywordsList.end( ) ) {
      case 0:
        status( );
        break;
    
      case 1:
        account( );
        break;
    
      default:
        std::cout << "method not found!!" << std::endl;
      }
    }
    

    Calculating a signature of the string.

    std::size_t myhash( const ::std::string &s ) {
      static ::std::hash< ::std::string > h{ };
    
      return h( s );
    }
    
    ...
    
    const KStatus = myhash( "status" );
    const KAccount = myhash( "account" );
    
    ...
    
    void redirect( const std::string &method_to_call ) {
      switch( myhash( method_to_call ) {
      case KStatus:
        status( );
        break;
    
      case KAccount:
        account( );
        break;
    
      default:
        std::cout << "method not found!!" << std::endl;
      }
    }
    

    Bonus: ::std::unordered_map< > .

    This is the most powerful method of all. And one of the fastest.

    void account( ) {
      ...
    }
    
    void status( ) {
      ...
    }
    
    ::std::unordered_map< ::std::string, ::std::function< void( ) > > Keywords{
      { "account", account },
      { "status", status }
    }
    
    ...
    
    void redirect( const std::string &method_to_call ) {
      auto iter = Keywords.find( method_to_call );
    
      if( iter == Keywords.end( ) ) {
        std::cout << "method not found!!" << std::endl;
      } else {
        iter->second( );
      }
    }
    
        
    answered by 09.09.2017 / 15:16
    source
    0

    I use a more complex method, to associate string with enum . There are a lot of methods, articles and code that does it. I'll explain more or less how the matter would be: You declare your enum type and an associated define :

    enum e_redirect {e_account = 0, e_status = 1};
    #define _e_redirect _T(",account=0,status=1,")
    

    And some data exchange routines:

    inline e_redirect eredirect (const CString &v)
    {  return ((e_redirect) iCvEnum(v,_e_redirect));}
    

    So your program would look like:

    void redirect(e_redirect ee)
    {
       switch (ee)
       {
          case e_account: account(); break;
          case e_status:  status();  break;
    
          default: 
             std::cout << "method not found!!" << std::endl; 
             break;
       }
    }
    
    int main()
    {
        std::string method_name;
        std::cin >> method_name;
    
        CString cs(method_name.c_str());
    
        redirect(eredirect(cs));
    }
    

    Make no mistake, it is still hardcoded but it is a trick that allows you to use switch instead of the if chained that dirty, in my opinion, much the code, also these routines allow you to do the reverse process, that is, you can get the string value of e_redirect but for that you need other routines that if you are interested, you tell me and I'll pass them to you.

    To use iCVEnum (v, _e_redirect) you will need these routines:

    bool bDameIzOde (CString &extremo, const CString &completo, const CString &sep=_sepSV, 
                     bool izdo=true, bool por_iz=true)
    {
       int           pos;
    
    
       // Posición del separador. ¿Se busca, por la izquierda o por la derecha?
       pos = (por_iz? completo.Find (sep) : completo.ReverseFind (sep[0]));
       if (pos == -1) return (false); // No ha encontrado ningún separador.
    
       if (izdo) // ¿Extremo izquierdo o el derecho?
            extremo = completo.Left (pos);
       else extremo = completo.Right (completo.GetLength() - (pos + sep.GetLength()));
    
       return (true);
    }
    
    inline int iInt (const CString &valor, int base = 0)
    {  // "nullptr" por que no quiero recuperar el resto de la cadena no convertida, 
       // "0" indica a "_tcstoul" que la base numérica se deduce de los primeros caracteres de la cadena
       return (_tcstoul(valor, nullptr, base));
    }
    
    inline int iCvEnum (CString v, const CString &def)
    {  // Valor comprendido entre ",valor string=" y el separador (,). "iInt" filtra el separador final
       return (bDameIzOde (v, def, _enu_sep + v + _enu_STRsepVAL, false) ? iInt(v) : _enu_ERR);
    }
    

    Yes, I use CString but I guess it will not be difficult to use the counterpart in string

        
    answered by 10.09.2017 в 11:48
    0

    If all the functions are going to be so simple, use a map of function pointers:

    using funciones_t = std::map<std::string, void(*)()>;
    
    funciones_t funciones
    {
        {"account", account},
        {"status", status},
    };
    

    With this function pointer map, you can change your redirect method as follows:

    void redirect(std::string method_to_call)
    {
        auto found = funciones.find(method_to_call);
        if (found != funciones.end())
        {
            (found->second)();
        }
        else
        {
            std::cout << "method not found!!\n";
        }
    }
    

    As you see, abuse of if is avoided and there is not a single hardcode string.

    You can see the code working in Wandbox 三 へ (へ ਊ) へ ハ ッ ハ ッ .

        
    answered by 12.09.2017 в 08:48
    0

    Finally I have chosen a mixture of several solutions that I have seen in stackoverflow, I implement the functions I want to execute in classes derived from action and I put the code inside the operator () and I include a pointer of the derived class in a map where I have the name of the classe and a pointer to it .. it is a bit gimmicky but I avoid totally hardcodear string's ..

    // STL
    #include <typeinfo>
    
    class action
    {
    public:
            static void add(action* act);
            static action* const get_action(const std::string& name);
            virtual const tx operator()(const std::vector<std::string>& params) = 0;
    
    private:
            static std::map<std::string, action*> action_list;
    };
    
    std::map<std::string, action*> action::action_list;
    
    // register new action
    void action::add(action* act)
    {
            if(act == nullptr)
                    return;
    
            action_list[typeid(*act).name()] = act;
    }
    
    action* const action::get_action(const std::string& name)
    {
            for(auto it = action_list.cbegin(); it != action_list.cend(); it++)
            {
                    if(it->first.find(name) != std::string::npos)
                            return it->second;
            }
    
            return nullptr;
    }
    
        
    answered by 12.09.2017 в 14:28