std::get on std::tuple приводит к тому, что переменные аргументы сводятся к нулю с ошибкой неполного типа

Следующий код выдает огромный список ошибок компилятора:

/// 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));
    }
};

Первая партия ошибок состоит из:

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<> >’

Таким образом, кажется, что вызов std::get заставляет другие части системных библиотек (не связанные напрямую с std::get или std::tuple) рекурсивно сводить Args... к нулю, и это происходит независимо от моей рекурсии числового параметра шаблона N. Есть ли что-то, что я могу исправить в своем коде, не меняя фундаментально свой подход, или просто невозможно использовать std::get в контексте, где параметры шаблона кортежа являются вариативными?


person realh    schedule 23.07.2017    source источник
comment
Вы неправильно подходите к проблеме. Что вам действительно нужно, так это std::index_sequence и достойный вызов .... Да, это инструмент C++14, но нетрудно найти/написать его реализацию для C++11.   -  person Nicol Bolas    schedule 23.07.2017
comment
Вы вызываете get<3> для кортежа с тремя элементами - конечно, это не компилируется. Три элемента извлекаются с get<0> по get<2>. У вас старая добрая ошибка с ошибкой.   -  person Igor Tandetnik    schedule 24.07.2017
comment
Я не сразу это заметил, спасибо. Но я уже изменил его на стиль, предшествующий C++11, предоставив отдельные шаблоны для ‹T1, T2, T3, T4›, ‹T1, T2, T3› и т. д. Не думаю, что мне понадобится больше чем 4, это проще, чем вариады, и чуть более многословно до 4.   -  person realh    schedule 24.07.2017
comment
@NicolBolas, ваше предложение интригует, но я не могу понять, как index_sequence поможет, не используя также выражение сворачивания пакета; возможно, это то, что вы имели в виду под «достойным ... вызовом»? Проблема в том, что последнее является функцией С++ 17. Это не проблема для меня, но может быть для людей, которые хотят создавать мой код на консервативных LTS/стабильных дистрибутивах Linux.   -  person realh    schedule 24.07.2017
comment
@realh: ... не является функцией C++17. Это функция С++ 11. Использование ... для выражений сворачивания — это функция C++17, но вы можете используйте ... в множестве других способов в C++11.   -  person Nicol Bolas    schedule 24.07.2017


Ответы (1)


Учитывая реализацию функции C++14 make_index_sequence (и связанных с ней типов), вы можете достаточно легко делать то, что хотите. :

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);
}

С помощью выражений свертки C++17 странные вещи в binder_helper можно свести к чему-то гораздо более разумному:

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)), ...);
}

Обратите внимание, что в последнем случае стандарт не гарантирует, что вызовы s.bind будут выполняться по порядку. В первом случае, поскольку выражения заключены в список init-list в фигурных скобках, вам гарантирована оценка по порядку.

person Nicol Bolas    schedule 24.07.2017
comment
Я вижу, С++ 11/14 также поддерживает основы выражений свертки, если вы помещаете их во что-то вроде списка аргументов. Это мне очень подходит. - person realh; 25.07.2017
comment
@realh: Нет, это не кратные выражения. Пожалуйста, прочитайте ссылки, которые я вам дал. Расширение Pack было частью C++11. Выражения Fold являются расширением расширений пакета. - person Nicol Bolas; 25.07.2017
comment
Я читал эти ссылки, поэтому, пожалуйста, перечитайте мой комментарий и поймите, что под сущностью я имел в виду существенное подмножество функций. - person realh; 26.07.2017
comment
@realh: И я говорю вам, что это не кратные выражения! Пакетные расширения — это надмножество выражений свертки, а не наоборот. - person Nicol Bolas; 26.07.2017
comment
Да, сначала я упустил разницу между выражениями расширения пакета и выражениями сгиба, но теперь я понимаю разницу, выраженную в коде, и принимаю ваш ответ. Мы просто не можем договориться о том, как выразить некоторую тривиальную семантику на английском языке, поэтому, пожалуйста, давайте просто оставим это. - person realh; 26.07.2017