rausted
rausted

Reputation: 959

std::tuple get everything except last element

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

Answers (2)

user7860670
user7860670

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

online compiler

Upvotes: 1

bolov
bolov

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

Related Questions