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:
Is there some way to invoke my std::function with the set of args generated by templated function from the vector? I looked at std::bind, but that didn't appear to offer a simple way to solve the problem, but maybe iterative binding of one arg at a time, deriving one function from another from another...
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?
I answered the VS brokenness myself with the following test:
#include <iostream>
#include <string>
struct OrderedCall
{
template <typename F, typename ...Args>
OrderedCall(F && f, Args &&... args)
{
std::forward<F>(f)(std::forward<Args>(args)...);
}
};
void foo(int, std::string s, char, bool) {
int i = 0;
i++;
}
int bar() {
return 10;
}
template <typename T>
T echo(T x) {
std::cout << " " << x;
return x;
}
int main(int argc, char**argv) {
std::string goo = "Goo";
std::cout << "Occurance: " << bar() << " " << goo << " " << 'x' << " " << false;
std::cout << std::endl << "Unordered call:";
foo(echo(bar()), echo(goo), echo('x'), echo(false));
std::cout << std::endl << "Ordered call:";
OrderedCall{ foo, echo(bar()), echo(goo), echo('x'), echo(false) };
std::cout << std::endl << "Try in Debug and Release modes. " << std::endl
<< "Ordered should match Occurance" << std::endl;
}
Apparently still broken in lastest update of Visual Studio 19, the following in Debug mode.
Occurance: 10 Goo x 0
Unordered call: Goo 0 x 10
Ordered call: 0 x 10 Goo
Try in Debug and Release modes.
Ordered should match Occurance
See: https://timsong-cpp.github.io/cppwp/n3337/dcl.init.list and https://timsong-cpp.github.io/cppwp/n3337/class.expl.init
- Within the initializer-list of a braced-init-list, the initializer-clauses, including any that result from pack expansions ([temp.variadic]), are evaluated in the order in which they appear. That is, every value computation and side effect associated with a given initializer-clause is sequenced before every value computation and side effect associated with any initializer-clause that follows it in the comma-separated list of the initializer-list. [ Note: This evaluation ordering holds regardless of the semantics of the initialization; for example, it applies when the elements of the initializer-list are interpreted as arguments of a constructor call, even though ordinarily there are no sequencing constraints on the arguments of a call. — end note ]
[The above note is in the text, not added by me.]