Я пытаюсь вернуть все, кроме последнего элемента в std::tuple, если в кортеже всего два элемента, вернуть первый. Поскольку std::tuple имеет много средств времени компиляции, тип с двойным возвратом должен быть выполнимым. Вот что у меня есть на данный момент:
// Behavior
// init(std::make_tuple(1,2)) = 1
// init(std::make_tuple(1,2,3)) = (1,2)
// First case
template<class T1, class T2>
inline static T1 init(Tuple<T1, T2> t) {
return std::get<0>(t);
}
// Second case
template<class ...Args, class S = std::make_index_sequence<sizeof...(Args) - 1>>
inline static decltype(auto) init(Tuple<Args...> t) {
return std::apply([](const auto &item...) {
return std::tuple_cat(std::make_tuple(std::get<S>) ... std::tuple<>);
}, t);
}
Было бы здорово, если бы я мог сделать это удобным для C++ 17 способом. Я получаю следующую ошибку с описанной выше реализацией:
./tuple.cpp:36:55: error: pack expansion does not contain any unexpanded parameter packs
return std::tuple_cat(std::make_tuple(std::get) ... std::tuple<>);~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^
1 error generated.





Не имеет отношения к вашей проблеме, но шаблон подразумевает inline, и вам не нужен static. Я действительно думаю, что с static вы нарушаете ODR. Почему вы их используете? Также вы можете сделать функции constexpr. Дальнейшее улучшение заключается в использовании (пересылке) ссылок и std::forward_as_tuple. Вот базовая реализация:
template <class... Args, std::size_t... Is>
constexpr auto init_helper(std::tuple<Args...> tp, std::index_sequence<Is...>)
{
return std::tuple{std::get<Is>(tp)...};
}
template <class... Args>
constexpr auto init(std::tuple<Args...> tp)
{
return init_helper(tp, std::make_index_sequence<sizeof...(Args) - 1>{});
}
auto test()
{
static_assert(init(std::tuple{1}) == std::tuple{});
static_assert(init(std::tuple{1, 2}) == std::tuple{1});
static_assert(init(std::tuple{1, 2, 3}) == std::tuple{1, 2});
}
В комментариях вы сказали, что хотите увидеть, возможно ли, что init(std::tuple{1,2}) вернет значение напрямую, а не кортеж из одного значения. С учетом того, что я бы не рекомендовал это, так как это приведет к непоследовательному поведению функции, да, это возможно. И C++17 делает это чрезвычайно чистым:
template <class... Args>
constexpr auto init(std::tuple<Args...> tp)
{
if constexpr (sizeof...(Args) == 2)
return std::get<0>(tp);
else
return init_helper(tp, std::make_index_sequence<sizeof...(Args) - 1>{});
}
auto test()
{
static_assert(init(std::tuple{1}) == std::tuple{});
static_assert(init(std::tuple{1, 2}) == 1);
static_assert(init(std::tuple{1, 2, 3}) == std::tuple{1, 2});
}
Разве constexpr не подразумевается? В любом случае, я согласен, и это кажется хорошим решением, но как вы думаете, я могу получить static_assert(init(std::tuple{1,2}) == 1) вместо унарного кортежа?
@rausted нет, constexpr не подразумевается. (я думаю, кроме лямбд, начиная с C++ 17). Да, вы можете получить это, но вы уверены, что хотите этого? Было бы несоответствие. Я обновлю свой ответ на этот случай.
Я согласен, что это непоследовательно, поскольку теперь init может возвращать как std::tuple<Args...>, так и общий T, но это интересный эксперимент с зависимой типизацией в метапрограммировании, если это возможно.
Идея заключалась бы в реализации вспомогательной функции, которая имела бы список индексов элементов исходного кортежа для копирования:
#include <tuple>
#include <utility>
#include <cstddef>
template<typename x_Tuple, ::std::size_t... x_index> auto
make_tuple_helper(x_Tuple const & other, ::std::index_sequence<x_index...>)
{
return ::std::make_tuple(::std::get<x_index>(other)...);
}
template<typename... x_Field> inline auto
cut_last_item(::std::tuple<x_Field...> const & other)
{
return make_tuple_helper(other, ::std::make_index_sequence<sizeof...(x_Field) - ::std::size_t{1}>{});
}
template<> inline auto
cut_last_item(::std::tuple<> const & other)
{
return other;
}
int main()
{
::std::tuple<int, short, float, double> t4{};
::std::tuple<int, short, float> t3{cut_last_item(t4)};
::std::tuple<int, short> t2{cut_last_item(t3)};
::std::tuple<int> t1{cut_last_item(t2)};
::std::tuple<> t0{cut_last_item(t1)};
::std::tuple<> t00{cut_last_item(t0)};
}
Почему вы используете ::std?
@Evgeny Это относится к пространству имен std в глобальном пространстве имен
Я это понимаю. Но что могло бы пойти не так, если бы это был просто std? Когда мне следует использовать ::std вместо std?
@Evgeny Идентификатор std не является специальным или зарезервированным. Просто std может относиться к чему-то объявленному во включающей области (например, к другому пространству имен, или классу, или перечислителю). В общем, рекомендуется всегда использовать префикс :: к именам из глобального пространства имен, чтобы они не меняли свое значение внезапно.
вы хотите вернуть кортеж со всеми элементами, кроме последнего?