Search code examples
c++templatesc++14variadic-templatesvariadic

Creating Generic Insert function


I was trying to create generic function that can take table name and values and return insert into query and have come up with something like below :

    struct any {
  enum type {Int, Float, String};
  any(int   e) { m_data.INT    = e; m_type = Int;}
  any(float e) { m_data.FLOAT  = e; m_type = Float;}
  any(char* e) { m_data.STRING = e; m_type = String;}
  type get_type() const { return m_type; }
  int get_int() const { return m_data.INT; }
  float get_float() const { return m_data.FLOAT; }
  char* get_string() const { return m_data.STRING; }
private:
  type m_type;
  union {
    int   INT;
    float FLOAT;
    char *STRING;
  } m_data;
};
template<typename ...Args>
std::string GetInsertString(const std::string& tableName, Args... args)
{
    std::string insertString = "INSERT INTO ";
    insertString += tableName;
    insertString += " VALUES(";
    std::vector<any> vec = {args...};
    std::ostringstream ss;
    for (unsigned i = 0; i < vec.size(); ++i)
    {
        switch(vec[i].get_type())
        {
            case any::Int:
                ss.str("");
                ss << vec[i].get_int();
                insertString += ss.str() + ",";
                break;
            case any::Float:
                ss.str("");
                ss << vec[i].get_float();
                insertString += ss.str() + ",";
                break;
            case any::String:
                ss.str("");
                insertString += "'" + std::string(vec[i].get_string()) + "'," ;
                break;
        }
    }
    insertString.pop_back();
    insertString += ");";
    return insertString;
}

where any is class based on this link How can I iterate over a packed variadic template argument list?

But the issue is I am not able to pass std::string type as variadic argument to this function since we have union in any class so need help from you guys to pass std::string type as argument for constructing values for insert query


Solution

  • You can use a virtual base class, a template wrapper and smart pointers a writing someting as follows

    #include <string>
    #include <vector>
    #include <memory>
    #include <sstream>
    #include <iostream>
    
    
    struct anyBase
     { virtual int unusedVirt () { return 0; }; };
    
    template <typename T>
    struct anyW : public anyBase
     { 
       T val;
    
       anyW (T const & v0) : val{v0} { }
     };
    
    
    struct any
     {
       public: 
          enum type { Int, Float, String };
    
          any (int e)
             : m_type{Int}, m_data{new anyW<int>(e)} { }
    
          any (float e)
             : m_type{Float}, m_data{new anyW<float>(e)} { }
    
          any (char const * e)
             : m_type{String}, m_data{new anyW<std::string>(e)} { }
    
          any (std::string const & e)
             : m_type{String}, m_data{new anyW<std::string>(e)} { }
    
          any (any const & a) : m_type{a.m_type}, m_data{nullptr}
           {
             switch ( m_type )
              {
                case Int:
                   m_data.reset(new anyW<int>(a.get_int()));
                   break;
    
                case Float:
                   m_data.reset(new anyW<float>(a.get_float()));
                   break;
    
                case String:
                   m_data.reset(new anyW<std::string>(a.get_string()));
                   break;
              }
           }
    
          type get_type () const { return m_type; }
    
          int get_int () const
           { return dynamic_cast<anyW<int>*>(m_data.get())->val; }
    
          float get_float () const
           { return dynamic_cast<anyW<float>*>(m_data.get())->val; }
    
          std::string const & get_string () const
           { return dynamic_cast<anyW<std::string>*>(m_data.get())->val; }
    
       private:
          type m_type;
    
          std::unique_ptr<anyBase> m_data;
     };
    
    
    template<typename ...Args>
    std::string GetInsertString(const std::string& tableName, Args... args)
     {
       std::string insertString = "INSERT INTO ";
       insertString += tableName;
       insertString += " VALUES(";
       std::vector<any> vec = {args...};
       std::ostringstream ss;
       for (unsigned i = 0; i < vec.size(); ++i)
        {
          switch(vec[i].get_type())
           {
             case any::Int:
                ss.str("");
                ss << vec[i].get_int();
                insertString += ss.str() + ",";
                break;
             case any::Float:
                ss.str("");
                ss << vec[i].get_float();
                insertString += ss.str() + ",";
                break;
             case any::String:
                ss.str("");
                insertString += "'" + std::string(vec[i].get_string()) + "'," ;
                break;
           }
        }
       insertString.pop_back();
       insertString += ");";
       return insertString;
     }
    
    
    int main ()
     {
       std::cout << GetInsertString("fooTable", 1, 2.2f, "3", std::string("4"))
          << std:: endl;
       // print INSERT INTO fooTable VALUES(1,2.2,'3','4');
     }
    

    Observe that:

    • this solution should work with C++11 too

    • I've unified the char * and the std::string cases; I think it's a bad idea register a chat *

    • using a std::unique_ptr you need the copy_constructor because the copy constructor in std::unique_ptr is deleted so is deleted the default copy constructor for any too

    • a better solution could be wait for the incoming (C++17, if I'm not wrong) std::variant