Pablo
Pablo

Reputation: 611

Fold on variadic template function to call a function on every template type while providing the order

I have recently discovered SQLite and it seems like a wonderful embedded DB to me. The only (and really small) issue that I can find on it is about its C interface and how it forces you to type a lot of boiler plate code, so I wonder if using variadic templates and C++17 fold features we can improve that point.

Unfortunately, I know what I want but I have not been able to develop it by myself.

Desirable preconditions for the solution:

  1. Solution based on C++17 or below (but sadly NOT C++20)
  2. It should be a high plus to get rid of the auxiliary template specialized functions (maybe a smart use of if constexpr(type_trait)?)

This is my current code, together with a commented example of how the variadic template function should behave:

#include <iostream>
#include <tuple>

// Fake mock pretending the interface for SQLite3 (just to avoid have to install, link and include the real project).
// Please, do NOT change anything here.
unsigned char buffer[] = { 't', 'e', 's', 't' };
const int sqlite3_column_int(int iCol) { return 5; }
const unsigned char* sqlite3_column_text(int iCol) { return buffer; }
// End of unmodificable mock.

// Auxiliary functions:
template<typename T>
T sqlite3_column(int col);

template<>
int sqlite3_column<int>(int col) { return sqlite3_column_int(col); }

template<>
std::string sqlite3_column<std::string>(int col) { return reinterpret_cast<const char*>(sqlite3_column_text(col)); }

// What I would like to have available:
template<typename... Args>
auto sqlite3_record() -> std::tuple<Args...>
{
    return std::make_tuple((sqlite3_column<Args>(N?),...));
}

//It should behabe as this example:
//std::tuple<int, int, std::string, std::string> sqlite3_record_example()
//{
//    return std::make_tuple( sqlite3_column<int>(0), sqlite3_column<int>(1), sqlite3_column<std::string>(2), sqlite3_column<std::string>(3) );
//}


int main()
{
    auto [i1, i2, s1, s2] = sqlite3_record<int, int, std::string, std::string>();
    std::cout << i1 << " / " << i2 << " / " << s1 << " / " << s2;
    //It should return 5 / 5 / test / test
}

Here is a Coliru link: http://coliru.stacked-crooked.com/a/52e5acd9d92a39e8

Please, forgive the horrible N? for the template parameter place.

Upvotes: 0

Views: 400

Answers (1)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275500

template<typename... Args, std::size_t...Is>
auto sqlite3_record(sqlite3_stmt* statement, std::index_sequence<Is...>)
{
  return std::make_tuple(sqlite3_column<Args>(statement, Is)...);
}
template<typename... Args>
auto sqlite3_record(sqlite3_stmt* statement){
  return sqlite3_record<Args...>(statement, std::make_index_sequence<sizeof...(Args)>{});
}

you just need to count, right?

To get a count of the packs, we make a 2nd pack with the count in it.

Then we can expand both packs at once, so long as they are the same size.

std::make_index_sequence<N>{} returns an object of type index_sequence<0, 1, ... , n-2, n-1>, so by matching in a function signature we can get a pack with the indexes we want.

The easiest way is to make a helper function to get at that pack, and do the double parameter pack expansion there, but there are other ways we could get at the pack of integers.

Upvotes: 1

Related Questions