Not a template error after all...
published at 12.06.2025 17:47 by Jens Weller
Save to Instapaper Pocket
I remember seeing the error, looking at the code and wondering what went wrong? And as the error triggered in a template function, I did wonder if I've hit an edge case where my code was subtly wrong. Or did a compiler upgrade make this emerge? The function in question did not error always, just a few times as a runtime error.
Which is the reason I did not have to deal with it directly, as I've been busy running Meeting C++ 2024. Now working again in this application to get the voting for Meeting C++ 2025 started, this error showed up in the logs again.
The called function is part of the code that executes SQL queries, and I've written about this code 10 years ago. Adding new queries to have more statistics turned out to trigger my "special case". In which this code triggers the logged error "parameter count mismatch".
q=EventDB_sql::exec_sql("SQL Statement where role = :r and event = :eid row LIKE '%something%';",EventDB::role::ATTENDEE,settings.current_event); if(q.next()) tickets_sold = q.value(0).toInt();
As you can see, aboves function takes an SQL Statement, and the arguments to it. This is driven by a function template and prior to this I've used a QVariantList to do this. The code hasn't changed, and as I wrote 10 years ago:
template<class ...Args > QSqlQuery exec_sql(const QString& sql, Args... args) { QSqlQuery query; query.prepare(sql); Q_ASSERT(query.boundValues().size() == sizeof...(args)); bind_value(query,0, args...); if(!query.exec() && query.lastError().isValid()) qWarning() << query.lastError().text()<< query.lastQuery(); return query; }
call: auto query = exec_sql(*sql*,foo,bar,"oh look a string",42);
This is the version I mostly use for selects, so it needs to return the query, and takes the SQL as a string. The actual binding of the values into the query happens with the function bind_value, which takes a query object by reference, and the starting index, which is always 0 (as all the values are in the parameter pack). The implementation of bind_value is very simple:
void bind_value(QSqlQuery&, int ); template<class T,class ...Args> void bind_value(QSqlQuery& query, int index,T t, Args... args) { query.bindValue(index,t); bind_value(query,++index,args...); }
This code is not fairly complicated, but the error message being parameter count mismatch with the code taking care of that this shouldn't happen made me wonder. After all, these parameters are handled by the template. But as the title already hints, the issue is a different one.
So far I thought that this code works as it should. And mostly it has, until I've got these a bit more complicated queries a short while before the conference. I've been to busy to look closer into this, but recently with working on this application for Meeting C++ 2025, this resurfaced. Last week I've had enough, and started looking into it.
As a matter of fact this code exactly works as it should, which then also makes the error message make sense when you think about it. What I didn't think about is, that QSqlQuery keeps on working when there is already a raised error. And that this than shadows the actual error causing this. See, in aboves code query.prepare is called, but this function actually does return a bool, which I forgot to check for. Which makes the fix for this a simple if/else:
template<class ...Args> QSqlQuery exec_sql(const QString& sql, Args... args) { QSqlQuery query; if(!query.prepare(sql)) qWarning() << query.lastError().text()<< query.lastQuery() << " driver text: " << query.driver()->lastError().text(); else { Q_ASSERT(query.boundValues().size() == sizeof...(args)); bind_value(query,0, args...); if(!query.exec() && query.lastError().isValid()) qWarning() << query.lastError().text()<< query.lastQuery() << " driver text: " << query.driver()->lastError().text(); } return query; }
In the refactoring I've also added the driver text to the logging, but so far that has not been helpful. And the actual error was one of the most common SQL errors one gets when writing queries: ambiguous column name: event_id Unable to execute statement.
I think in the past I've usually found the error in the sql statement, and hence never got bothered by this bug. For the future I will now see the actual error instead of a cryptic error message.
I've also learned from this to weary of APIs with lastError interfaces. This will show you the last error, but its your responsibility to make sure to handle always the last possible error. Unlike exceptions, which you can handle in one place, this forces you to check for potential errors along your code path.
Join the Meeting C++ patreon community!
This and other posts on Meeting C++ are enabled by my supporters on patreon!