I am trying to simplify a C/C++ codebase that invokes SQL Server DB statements and/or stored procedures from SQL Server by writing variadic templates of various sorts. I have been successful writing a doQuery function that accepts additional parameters that are automatically bound to the query by type, e.g. boolean, double, string, as input or input/output arguments.
The current problem:
For queries that return result sets, I am struggling to produce optimal simple template calls.
I have tried tuples, but they are unpopular among a traditional C developer team, and I don't like them myself.
So I accept a function that is called back with the data of each row which the user can choose to push/init{} in any form, e.g. push into a captured vector of structs. From the signature of the callback, I generate the SQLBindCol bindings for ODBC to nth degree of complexity into an array of binding information for the returning columns.
e.g.
std::vector<MyStruct> result;
auto functor = [&result](int id, string name, bool isOK, string address) {
result.push_back(MyStruct{id,name,isOK,address});
}
c.doQuery(functor, "select id, name, isOK, address from MyTable;" /**/);
(/**/ shows where parameters can be supplied to fill in '?', which I have working beautifully, but this simple example has none).
The problem comes trying to invoke the function reassociating query row results:
template <typename T...>
InternalFunction(T)
[...]
vector<Binding> columns;
[...]
int i = 0;
functor(GetResult<T>(columns, i++)...);
GetResult returns the column from the ith member of the binding vector as the desired type into the function call. But C++ does not guarantee order of evaluation of arguments. So according to https://stackoverflow.com/users/596781/kerrek-sb in the thread How to guarantee order of argument evaluation when calling a function object?, the solution is fairly simple:
struct OrderedCall
{
template <typename F, typename ...Args>
OrderedCall(F && f, Args &&... args)
{
std::forward<F>(f)(std::forward<Args>(args)...);
}
};
[...]
OrderedCall{functor, GetResult<T>(columns, i++)...};
Unfortunately, Visual Studio 15 (MSVC) is apparently broken and still reorders the evaluations, e.g. if there is a string argument, that gets evaluated last, making the results come from arbitrarily selected members of the column vector instead of picking them up in order as they were auto-bound using the variadic methods.
I do not know of any other way to associate the arguments with successive vector entries to solve this problem and make the functor callback work reliably.
If I disable optimization and function inlining and remove the OrderedCall struct trick, the evaluations will occur fairly reliably in reverse order which is usable, but is not a good solution because it is ultimately neither practical nor reliable.
I cannot think of another way to invoke the function while: re-associating arguments with columns, preserving types of the arguments, etc.
The whole point is to make the binding simple and natural, which is why tuples are ruled out together with other solutions that uglify the calling pattern.
So my questions are:
Should I engineer a recursive template just for the purposes of invoking the functor? (it would be an ugly implementation at best passing arguments through many layers of complexity, if it works at all).
Did I miss a simpler/better solution for making it dead simple to push result rows into a vector of structs?
Am I re-inventing the wheel? Is this variadic binding simplification already available for sql-server / odbc / msvc?
Is a later version of Visual Studio not broken like this?
Aucun commentaire:
Enregistrer un commentaire