0

The following code produces a huge list of compiler errors:

/// Uses template recursion to bind all args
template<std::size_t N, typename... Args> class Binder
{
public:
    Binder(Sqlite3StatementBase &s, std::tuple<Args...> &tup)
    {
        Binder<N - 1, Args...> b(s, tup);
        s.bind(N + 1, std::get<N, Args...>(tup)); // Line 182
    }
};

/// Specialization of Binder to end recursion at 0
template<typename... Args> class Binder<0, Args...>
{
public:
    Binder(Sqlite3StatementBase &s, std::tuple<Args...> &tup)
    {
        s.bind(1, std::get<0, Args...>(tup));
    }
};

The first batch of errors consists of:

In file included from /usr/include/c++/6/bits/unique_ptr.h:37:0,
             from /usr/include/c++/6/condition_variable:43,
             from /home/tony/htpc/Dev/logi/src/db/logi-db.h:22,
             from /home/tony/htpc/Dev/logi/src/db/logi-sqlite.h:26,
             from /home/tony/htpc/Dev/logi/src/db/logi-sqlite.cpp:24:
/usr/include/c++/6/tuple: In instantiation of ‘class std::tuple_element<1ul, std::tuple<unsigned int> >’:
/usr/include/c++/6/tuple:1228:12:   recursively required from ‘class std::tuple_element<2ul, std::tuple<unsigned int, unsigned int> >’
/usr/include/c++/6/tuple:1228:12:   required from ‘class std::tuple_element<3ul, std::tuple<unsigned int, unsigned int, unsigned int> >’
/usr/include/c++/6/utility:106:69:   required by substitution of ‘template<long unsigned int __i, class _Tp> using __tuple_element_t = typename std::tuple_element::type [with long unsigned int __i = 3ul; _Tp = std::tuple<unsigned int, unsigned int, unsigned int>]’
/usr/include/c++/6/tuple:1270:5:   required by substitution of ‘template<long unsigned int __i, class ... _Elements> constexpr std::__tuple_element_t<__i, std::tuple<_Elements ...> >&& std::get(std::tuple<_Elements ...>&&) [with long unsigned int __i = 3ul; _Elements = {unsigned int, unsigned int, unsigned int}]’
/home/tony/htpc/Dev/logi/src/db/logi-sqlite.h:182:47:   required from ‘logi::Sqlite3Database::Binder<N, Args>::Binder(logi::Sqlite3Database::Sqlite3StatementBase&, std::tuple<_Elements ...>&) [with long unsigned int N = 3ul; Args = {unsigned int, unsigned int, unsigned int}]’
/home/tony/htpc/Dev/logi/src/db/logi-sqlite.h:238:71:   required from ‘void logi::Sqlite3Database::Sqlite3Statement<Args>::prepare_row(logi::Sqlite3Database::Sqlite3Statement<Args>::Tup&) [with Args = {unsigned int, unsigned int, unsigned int}; logi::Sqlite3Database::Sqlite3Statement<Args>::Tup = std::tuple<unsigned int, unsigned int, unsigned int>]’
/home/tony/htpc/Dev/logi/src/db/logi-sqlite.h:231:28:   required from ‘void logi::Sqlite3Database::Sqlite3Statement<Args>::execute(typename logi::Sqlite3Database::Sqlite3Statement<Args>::Parent::ArgsVector&) [with Args = {unsigned int, unsigned int, unsigned int}; typename logi::Sqlite3Database::Sqlite3Statement<Args>::Parent::ArgsVector = std::vector<std::tuple<unsigned int, unsigned int, unsigned int>, std::allocator<std::tuple<unsigned int, unsigned int, unsigned int> > >]’
/home/tony/htpc/Dev/logi/src/db/logi-sqlite.cpp:354:1:   required from here
/usr/include/c++/6/tuple:1228:12: error: invalid use of incomplete type ‘class std::tuple_element<0ul, std::tuple<> >’

So it seems as if the std::get call is causing other parts of the system libraries (not directly related to std::get or std::tuple) to recursively reduce Args... to nothing, and this is happening independently of my recursion of the numerical template parameter N. Is there something I can fix in my code without fundamentally changing my approach, or is it just not possible to use std::get in a context where the tuple's template parameters are variadic?

realh
  • 962
  • 2
  • 7
  • 22
  • 1
    You're approaching the problem the wrong way. What you really want is [`std::index_sequence`](http://en.cppreference.com/w/cpp/utility/integer_sequence) and a decent `...` invocation. Yes, that's a C++14 tool, but it's not hard to find/write an implementation of it for C++11. – Nicol Bolas Jul 23 '17 at 19:06
  • 1
    You are calling `get<3>` on a tuple with three elements - of course that doesn't compile. The three elements are retrieved via `get<0>` through `get<2>`. Yours is a good old-fashioned off-by-one error. – Igor Tandetnik Jul 24 '17 at 01:59
  • I didn't spot that off-by-one, thanks. But I've already changed it to a pre-C++11 style, providing individual templates for , etc. I don't think I'll need more than 4, it's simpler than variadics and barely more verbose for up to 4. – realh Jul 24 '17 at 13:00
  • @NicolBolas, your suggestion is intriguing, but I can't work out how `index_sequence` would help without also using a pack fold expression; perhaps that's what you meant by 'decent `...` invocation'? The trouble is, the latter is a C++17 feature. That's not a problem for me, but might be for people who want to build my code on conservative LTS/stable Linux distros. – realh Jul 24 '17 at 15:19
  • 1
    @realh: `...` is *not* a C++17 feature. It's a C++11 feature. Using `...` for [fold expressions](http://en.cppreference.com/w/cpp/language/fold) is a C++17 feature, but you can use `...` in [plenty of other ways in C++11](http://en.cppreference.com/w/cpp/language/parameter_pack). – Nicol Bolas Jul 24 '17 at 15:20

1 Answers1

1

Given an implementation of the C++14 make_index_sequence function (and its associated types), you can do what you want easily enough:

template<size_t ...indices, typename ...Args>
void binder_helper(Sqlite3StatementBase &s, std::integer_sequence<size_t, indices...>, std::tuple<Args...> &tup)
{
    auto dump = {(s.bind(indices, std::get<indices>(tup)), 0)...};
}

template<typename ...Args>
void binder(Sqlite3StatementBase &s, std::tuple<Args...> &tup)
{
    binder_helper(s, std::make_index_sequence<sizeof...(Args)>(), tup);
}

With C++17's fold expressions, the oddball stuff in binder_helper can be reduced to something much more reasonable:

template<size_t ...indices, typename ...Args>
void binder_helper(Sqlite3StatementBase &s, std::integer_sequence<size_t, indices...>, std::tuple<Args...> &tup)
{
    (s.bind(indices, std::get<indices>(tup)), ...);
}

Note that in the latter case, the standard does not guarantee thatt the calls to s.bind will be executed in order. In the former case, because the expressions are wrapped in a braced-init-list, you're guaranteed in-order evaluation.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • I see, C++11/14 also support the essentials of fold expressions as long as you put them in something like an argument list. This suits me very well. – realh Jul 25 '17 at 11:55
  • @realh: No, those are not fold expressions. Please *read* the links I gave you. Pack expansion was part of C++11. Fold expressions are an expansion of pack expansions. – Nicol Bolas Jul 25 '17 at 13:25
  • I did read those links, so please re-read my comment and understand that by, "the essentials of", I meant "an essential subset of features of". – realh Jul 25 '17 at 23:06
  • @realh: And I'm telling you that's not fold expressions! Pack expansions are a *superset* of fold expressions, not the other way around. – Nicol Bolas Jul 25 '17 at 23:07
  • Yes, I missed the difference between pack expansion and fold expressions at first, but I do now understand the difference expressed in **code**, and I accept your answer. We just can't agree on how to express some trivial semantics in English, so please let's just leave it. – realh Jul 26 '17 at 13:19