Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a new tuple class by popping the last type

I tried the following code but it gives:

main.cpp:29:22: error: aggregate 'pop<std::tuple<int, char, float> > p' has incomplete type and cannot be defined

What am I missing?

template <typename T>
struct pop;

template <typename E, typename... Ts>
struct pop<tuple<Ts..., E>> {
    using result = tuple<Ts...>;
};

tuple<int, char, float> t;
typename pop<decltype(t)>::result p;

If Ts... must be at the end in a type list, why does it work in this example from http://en.cppreference.com/w/cpp/language/parameter_pack:

template<class A, class B, class...C> void func(A arg1, B arg2, C...arg3)
{
    container<A,B,C...> t1;  // expands to container<A,B,E1,E2,E3> 
    container<C...,A,B> t2;  // expands to container<E1,E2,E3,A,B> 
    container<A,C...,B> t3;  // expands to container<A,E1,E2,E3,B> 
}
like image 321
Benji Mizrahi Avatar asked Dec 01 '14 14:12

Benji Mizrahi


3 Answers

tuple<Ts..., E> is a non-deduced context. [temp.deduct.type]/9:

If P has a form that contains <T> or <i>, then each argument Pi of the respective template argument list P is compared with the corresponding argument Ai of the corresponding template argument list of A. If the template argument list of P contains a pack expansion that is not the last template argument, the entire template argument list is a non-deduced context.

That means that your partial specialization is never matched.

With C++14, one could use

template <class T, class=std::make_index_sequence<std::tuple_size<T>::value-1>>
struct pop;

template <typename Tuple, std::size_t... indices>
struct pop<Tuple, std::index_sequence<indices...>>
{
    using type = std::tuple<std::tuple_element_t<indices, Tuple>...>;
};

template <typename T>
using pop_t = typename pop<T>::type;

Such that

using t = std::tuple<int, char, float>;
static_assert( std::is_same<pop_t<t>, std::tuple<int, char>>{}, "" );

Compiles.

Demo.

like image 91
Columbo Avatar answered Oct 09 '22 00:10

Columbo


Ts... must be the last element of a type list if you want it to be deduced. tuple<Ts...,E> will not deduce Ts... to be all but the last, but rather never match anything.

Getting rid of the last argument is a bit tricker. live example:

#include <iostream>
#include <tuple>
#include <iostream>

namespace details {
  template<class Lhs, class Rhs>
  struct pop_helper;

  template<template<class...>class Tup, class L0, class...Lhs, class...Rhs>
  struct pop_helper<Tup<L0,Lhs...>, Tup<Rhs...>>:
    pop_helper<Tup<Lhs...>, Tup<Rhs...,L0>>
  {};
  template<template<class...>class Tup, class L0, class...Rhs>
  struct pop_helper<Tup<L0>, Tup<Rhs...>> {
    using type=Tup<Rhs...>;
  };
}

template <typename T>
struct pop;

template<template<class...>class Tup, class...Ts>
struct pop<Tup<Ts...>>:
  details::pop_helper<Tup<Ts...>,Tup<>>
{};

template<typename T>
using pop_t=typename pop<T>::type;

std::tuple<int, char, float> t;
typedef pop_t<decltype(t)> p;

int main() {
  p x = std::make_tuple( 7, 'x' );
  std::cout << std::get<0>(x) << std::get<1>(x) << std::tuple_size<p>{} << "\n";
}

pop_helper moves the types over one at a time to the right hand side, until there is only one type left on the left hand side. Then it returns the right hand side type.

pop just passes the tuples over.

I used template<class...>class Tup instead of std::tuple, because why not support almost every template instead of just std::tuple?

pop_t gets rid of the annoying typename spam at point of use.

I use the inhertance-as-type-map-forwarding pattern, which saves on typing. With a type-map, the structure:

template<class X>
struct bob: foo<X> {};

can be read as bob<X> is foo<X>. The alternative is the more verbose

template<class X>
struct bob {
  using type = typename foo<X>::type;
};

expanding variardic type lists is different than matching them. When it was designed, matching was kept simple in order to make compiler vendors be able to implement the feature. There may even be thorny issues beyond "it is tricky" down that path.

like image 3
Yakk - Adam Nevraumont Avatar answered Oct 09 '22 01:10

Yakk - Adam Nevraumont


Another C++11 way to skin this cat:

#include <tuple>

template<class Tuple>
struct pop;

template<>
struct pop<std::tuple<>>
{
    using type = std::tuple<>;
};

template<typename T1>
struct pop<std::tuple<T1>>
{
    using type = std::tuple<>;
};

template<typename First, typename... Rest>
struct pop<std::tuple<First,Rest...>>
{
    using type = 
    decltype(std::tuple_cat(
                std::declval<std::tuple<First>>(),
                std::declval<typename pop<std::tuple<Rest...>>::type>()));
};

// Test...

static_assert(std::is_same<pop<std::tuple<>>::type,std::tuple<>>::value,"");
static_assert(std::is_same<pop<std::tuple<int>>::type,std::tuple<>>::value,"");
static_assert(
    std::is_same<pop<std::tuple<int,char>>::type,std::tuple<int>>::value,"");
static_assert(
    std::is_same<pop<std::tuple<int,char,float>>::type,
        std::tuple<int,char>>::value,"");
static_assert(
    std::is_same<pop<std::tuple<int,char,float,double>>::type,
        std::tuple<int,char,float>>::value,"");
like image 1
Mike Kinghan Avatar answered Oct 09 '22 01:10

Mike Kinghan