0

I'm working on my first C++ project. I have 2 classes: 1 for the interaction with the sqlite db, the other one for the qt main window. In the main I create a new window. In the window constructor I would like to load the content of the db and display it in a QtWidget.

So if I understand well sqlite callback function will be called for each row that the sqlite3_exec returns. I made a select_all function in the database class which takes a callback function as an argument so I'll be able to do use the same sql function to display/use the data in different ways.

#include <cstdio>
#include <iostream>
#include <QtCore>
#include <QtGui>
#include <QtWidgets>
#include <qmainwindow.h>
#include <qstandarditemmodel.h>
#include <sqlite3.h>
#include <string>

using namespace std;

class Database {
public:
  sqlite3* db;
  Database() {db = create_or_open_database();}
  
  sqlite3* create_or_open_database()
  {
    sqlite3 *db = NULL;
    const char *query;
    int ret = 0;
    char *errorMsg = 0;

    ret = sqlite3_open("expense.db", &db);
    query = "CREATE TABLE IF NOT EXISTS EXPENSES(NAME TEXT KEY NOT NULL, AMOUNT INT NOT NULL, TAG TEXT, SUBTAG TEXT, DATE CHAR(10) NOT NULL);";
    ret = sqlite3_exec(db, query, callback, 0, &errorMsg);
    return db;
  }
  
  void select_all(int (*f)(void*, int, char**, char**)){
    int ret = 0;
    char *errorMsg = 0;
    
    const char *query = "SELECT * FROM EXPENSES";
    ret = sqlite3_exec(db, query, (*f), 0, &errorMsg);
  }
};


class MainWindow
{
public:
  QWidget window;
  Database expenses;
  QTreeView *navigateView = new QTreeView;
  QTreeView *expensesList = new QTreeView;
  QPushButton *newButton = new QPushButton;
  QVBoxLayout *mainVLayout = new QVBoxLayout;
  QHBoxLayout *listHLayout = new QHBoxLayout;
  QStandardItemModel *expensesModel = new QStandardItemModel;
  
  MainWindow()
  {
    QSizePolicy navigateSize(QSizePolicy::Preferred, QSizePolicy::Preferred);
    QSizePolicy expenseListSize(QSizePolicy::Preferred, QSizePolicy::Preferred);
    navigateSize.setHorizontalStretch(1);
    navigateView->setSizePolicy(navigateSize);
    expenseListSize.setHorizontalStretch(2);
    expensesList->setSizePolicy(expenseListSize);
    newButton->setText("New");
    listHLayout->addWidget(navigateView);
    listHLayout->addWidget(expensesList);
    mainVLayout->addLayout(listHLayout);
    mainVLayout->addWidget(newButton);
    window.setLayout(mainVLayout);

    // int (MainWindow::*foo)(void*, int, char**, char**) = &MainWindow::display_expenses_in_list;
    // expenses.select_all(foo);
    expenses.select_all(this->display_expenses_in_list);
  }

  int display_expenses_in_list(void *NotUsed, int argc, char **argv, char **azColName)
  {
    QStringList list = {"Name", "Amount (€)", "Tag", "Subtag", "Date"};
    this->expensesModel->setVerticalHeaderLabels(list);
    // here I'll create items and add them to the QTreeView
    return 0;
  }
};    
int main(int argc, char* argv[])
{
  QApplication app(argc, argv);
  MainWindow ui;
  ui.window.show();
 
  return app.exec(); 
}

With this code I get reference to a non-static member function must be called [bound_member_function]

If googled it and tried, I guess, to create a function pointer foo that point to the callback function (the lines that are currently commented). I get this : Cannot initialize a parameter of type 'int (*)(void *, int, char **, char **)' with an lvalue of type 'int (MainWindow::*)(void *, int, char **, char **)' [init_conversion_failed]

If I make display_expenses_in_list static then I can't edit the expensesModel...

Jason Aller
  • 3,541
  • 28
  • 38
  • 38

2 Answers2

0

The key here is that void* argument to sqlite3_exec. You now pass 0, but you need to pass this instead.

You can now make display_expenses_in_list static. That NotUsed parameter then becomes used. You just need to cast it back to MainWindow* and use it instead of this.

MSalters
  • 173,980
  • 10
  • 155
  • 350
0

The problem:

The problem is your display_expenses_in_list(...) function is a class member function. Therefore your need to use a pointer to a member function rather than a pointer to a function, because it must always be called on an instance of the class it's a member of - however the sqlite library will only take a void* function pointer.

Check out this article: https://isocpp.org/wiki/faq/pointers-to-members

The fix:

Modern C++ to the rescue here. Wrap up your whole class member function call up in a lambda with the class instance in scope, then pass a pointer to this new, anonymous function as the callback.

Check this stackoverflow answer out showing how to do it (copied below): https://stackoverflow.com/a/31461997/410072

An example (copied from the linked answer):

if (sqlite3_exec(this->db, this->SQL_SELECT_READINGS_QUERY, 
    +[](void* instance, int x, char** y, char** z) {
        return static_cast<dataSend_task*>(instance)->callback(x, y, z);
    },
    this,
    &err))
{
    /* whatever */
}
James
  • 30,496
  • 19
  • 86
  • 113
  • Thank you for your answer. If I get what you meant I need to declare `display_expenses_in_list()` as a lambda function then I'll be able to convert it to a function pointer instead of a member function pointer? I might need some time to dig into that ;) –  Nathoufresh Mar 31 '20 at 15:35
  • You're welcome, nearly, you can *wrap* `display_expenses_in_list()` in a lambda function which you can call it normally in with a class instance i.e. `myInstance->display_expenses_in_list()`. You can then pass a pointer to the wrapping lambda function via a `void*` as the callback. This answer is exactly what you need: https://stackoverflow.com/a/31461997/410072 – James Mar 31 '20 at 16:08