1

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.

2
  • you want to return a tuple with all elements except last? Commented Aug 12, 2018 at 16:34
  • std::ignore could help you I'd guess, maybe. Commented Aug 12, 2018 at 17:21

2 Answers 2

1

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});
}
Sign up to request clarification or add additional context in comments.

3 Comments

Isn't constexpr implicit? In any case, I agree and this seems like a good solution, but do you think I can get static_assert(init(std::tuple{1,2}) == 1) instead of a unary tuple?
@rausted no, constexpr is not implicit. (except for lambdas since c++17 I think). Yes you can get that, but are you sure you want that? It would be an inconsistency. I will update my answer for this case.
I agree it's inconsistent, since now init can return both std::tuple<Args...> and a generic T but its an interesting experiment with dependent typing in metaprogramming, if its possible.
1

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

4 Comments

Why do you use ::std?
@Evgeny It refers to namespace std at global namespace
I understand that. But what could've gone wrong if it were just std? When should I use ::std instead of std?
@Evgeny Identifier std is not special or reserved. Just std it may refer to something declared in enclosing scope (for example to another namespace or to class or to enumerator). In general it a good idea to always prefix names from global namespace with :: so they won't change their meaning all of it sudden.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.