Reputation: 959
I'm trying to return everything except the last element in an std::tuple
, if there are only two elements in the tuple, return the first one. Since std::tuple
has a lot of compile-time facilities the dual-return type should be do-able. Here's what I have so far:
// 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);
}
It would be great if I could do this in a c++17 friendly way. I'm getting the following error with the above implementation:
./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.
Upvotes: 1
Views: 1327
Reputation: 37600
The idea would be to implement a helper function that would have a list of indexes of source tuple items to be copied:
#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)};
}
Upvotes: 1
Reputation: 75815
Not related to your issue, but a template implies inline
and you don't want the static
. I actually think with static
you violate ODR. Why do you use them? Also you can make the functions constexpr
. A further improvement is to use (forwarding) references and std::forward_as_tuple
. Here is a basic implementation:
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});
}
You said in the comments that you want to see if it's possible that init(std::tuple{1,2})
to return the value directly instead of a tuple of one value. With the notice that I wouldn't recommend this as it will make the function behave inconsistent yes, it is possible. And C++17
makes this extremely clean:
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});
}
Upvotes: 1