Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is a safe way to increment an int in a variadic template expansion?

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...
like image 330
Oz. Avatar asked Apr 02 '13 03:04

Oz.


2 Answers

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.

like image 154
mfontanini Avatar answered Oct 20 '22 07:10

mfontanini


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.

like image 25
jogojapan Avatar answered Oct 20 '22 08:10

jogojapan