I am trying to implement a C++11 wrapper around an SQL library written in C. The C library has separate functions for getting different data types from an SQL statement which require a column index. A simple approach is prototyped below but has a critical flaw: it relies on the order of argument execution, which is not safe (may also have compiler errors, haven't tested it).
Question: What is a platform-independent way to safely increment a variable in a variadic template expansion?
template< typename... ColumnTypes >
void SQLStatement::execute( std::function< void( ColumnTypes... ) > rowCallback ){
while( this->nextRow() ){
int column = 0;
rowCallback( this->getColumn< ColumnTypes >( column++ )... );
// unreliable increment ^
}
}
template< typename T >
T SQLStatement::getColumn( const int columnIdx ){}
template<>
inline int SQLStatement::getColumn< int >( const int columnIdx ){
return sql_library_column_int( this->nativeHandle, columnIdx );
}
// Other getColumn specializations here...
This seems to work. You'd just have to adapt a couple of things:
#include <functional>
#include <iostream>
#include <cstddef>
void foo(int a, float b, int c) {
std::cout << a << ", " << b << ", " << c << std::endl;
}
template<typename T>
T getColumn(int index) {
return T(index);
}
template<size_t... indexes>
struct index_tuple {};
template<size_t head, size_t... indexes>
struct index_tuple<head, indexes...> {
typedef typename index_tuple<head-1, head-1, indexes...>::type type;
};
template<size_t... indexes>
struct index_tuple<0, indexes...> {
typedef index_tuple<indexes...> type;
};
template<typename... Args>
struct make_index_tuple {
typedef typename index_tuple<sizeof...(Args)>::type type;
};
template<typename... ColumnTypes, size_t... indexes>
void execute(const std::function<void(ColumnTypes...)> &callback, index_tuple<indexes...>) {
// this should be done for every row in your query result
callback(getColumn<ColumnTypes>(indexes)...);
}
template<typename... ColumnTypes>
void execute(const std::function<void(ColumnTypes...)> &callback) {
execute(
callback,
typename make_index_tuple<ColumnTypes...>::type()
);
}
int main() {
std::function<void(int, float, int)> fun(foo);
execute(fun);
}
Demo here. Note that the function foo
is only used to show that the indexes are correctly incremented, just like the return T(index);
in getColumn
.
While mfontanini's solution works and is good because it performs the calculation of the incrementing column index at compile time, I think it's worthwhile pointing out that there is also a direct answer to question of how to increment an int in a variadic pack expansion. (Unfortunately it does not seem to work on GCC due to a bug, see the caveat at the end.)
The answer is based on the fact that while the evaluations of arguments in a function call are unsequenced, the evaluations of arguments in a list initialization are not:
(§8.5.4/4) Within the initializer-list of a braced-init-list, the initializer-clauses, including any that result from pack expansions (14.5.3), 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 ]
Therefore, if you transform your function call into something based on a brace-init-list, you'll get the desired effect:
rowCallback(std::tuple<ColumnTypes...> { getColumn<ColumnTypes>(column++)... });
This initializes a std::tuple
using list-initialization (notice the braces { ... }
), hence the column++
side effect will be performed in left-to-right order.
If written as above, this means you need to change rowCallback()
to make it accept a std::tuple
instead of a list of arguments. If you don't like this, you can create a separate template function call_on_tuple(fun,tup)
, which calls any function fun
on the arguments that result from expanding tuple tup
. I once described how to do this here, or if you like, you could use rlxutil::call_on_tuple
from my GitHub repository.
Your execute
function then looks like this:
template <typename... ColumnTypes>
void execute(function<void(ColumnTypes...)> rowCallback)
{
using std::tuple;
using rlxutil::call_on_tuple;
int column = 0;
call_on_tuple(rowCallback,
tuple<ColumnTypes...> { getColumn<ColumnTypes>(column++)... });
}
Caveat: This is does not work as expected with GCC. I believe this is because of the bug reported here: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=51253.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With