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?
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...>;
};
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With