Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Attempt to remove last type from a tuple is failing

I'm trying to remove the last element of a tuple. It works when I have only one element in the tuple to remove. But when I have more than one, things go wrong. I can't get why this isn't working. These are the errors I'm getting:

prog.cpp: In function ‘int main()’:
prog.cpp:24:22: error: incomplete type ‘remove_last<std::tuple<int, int> >’ used in nested name specifier
prog.cpp:24:22: error: incomplete type ‘remove_last<std::tuple<int, int> >’ used in nested name specifier
prog.cpp:24:70: error: template argument 1 is invalid

#include <tuple>
#include <type_traits>

template <class T>
struct remove_last;

template <class T>
struct remove_last<std::tuple<T>>
{
    using type = std::tuple<>;
};

template <class... Args, typename T>
struct remove_last<std::tuple<Args..., T>>
{
    using type = std::tuple<Args...>;
};

int main()
{
    std::tuple<int, int> var;

    static_assert(
        std::is_same<remove_last<decltype(var)>::type,
        std::tuple<int>>::value, "Values are not the same"
    );
}

The errors go away when I make the template argument non-variadic in one of the specializations. But then that becomes a specialization which will only process a tuple with two elements - not what I was aiming for. How can I get this to work with variadic arguments? In other words, how can I get this to work when there is more than one element in the tuple?

like image 899
David G Avatar asked Apr 28 '13 21:04

David G


2 Answers

The problem is that the argument pack is greedy and - since it comes first - eats up all the types in the sequence when performing type deduction, including the T you expect to be left out of Args....

You could define the variadic specialization this way (notice that the argument pack is now appearing last in std::tuple<T, Args...>):

template <class T, class... Args>
struct remove_last<std::tuple<T, Args...>>
{
    using type = typename concat_tuple<
        std::tuple<T>,
        typename remove_last<std::tuple<Args...>>::type
        >::type;
};

And have the concat_tuple meta-function defined this way:

template<typename, typename>
struct concat_tuple { };

template<typename... Ts, typename... Us>
struct concat_tuple<std::tuple<Ts...>, std::tuple<Us...>>
{
    using type = std::tuple<Ts..., Us...>;
};
like image 69
Andy Prowl Avatar answered Oct 12 '22 14:10

Andy Prowl


An alternative solution, requires C++14 or newer:

#include <tuple>

template<class Tuple>
struct remove_last;

template<>
struct remove_last<std::tuple<>>; // Define as you wish or leave undefined

template<class... Args>
struct remove_last<std::tuple<Args...>>
{
private:
    using Tuple = std::tuple<Args...>;

    template<std::size_t... n>
    static std::tuple<std::tuple_element_t<n, Tuple>...>
        extract(std::index_sequence<n...>);

public:
    using type = decltype(extract(std::make_index_sequence<sizeof...(Args) - 1>()));
};

template<class Tuple>
using remove_last_t = typename remove_last<Tuple>::type;

See https://en.cppreference.com/w/cpp/utility/integer_sequence

like image 41
Vladimir Reshetnikov Avatar answered Oct 12 '22 14:10

Vladimir Reshetnikov