2

Is there a way to remove one of the types from type expansion?

Let's use tuple as an example,

void foo() {
     tuple<int, double, string> t;
     // some facility to expand except the i-th type:
     expand_except_ith<0>(t);   // returns tuple<double, string>
     expand_except_ith<1>(t);   // returns tuple<int, string>
}
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
Candy Chiu
  • 6,579
  • 9
  • 48
  • 69

3 Answers3

4

Here is a short version:

template<std::size_t...Is>
auto indexer( std::index_sequence<Is...> ) {
  return [](auto && f) {
    return f( std::integral_constant<std::size_t, Is>{}... );
  };
}
template<std::size_t N>
auto indexer() {
  return indexer( std::make_index_sequence<N>{} );
}

template<std::size_t N, class T>
auto except_nth( T&& t ) {
  constexpr auto size = std::tuple_size< std::decay_t<T> >{};
  static_assert( N < size, "No skipping past the end" );
  auto before = indexer<N>();
  auto after = indexer< size-N-1 > ();
  return before( [&]( auto...As ) {
    return after( [&]( auto...Bs ) {
      return std::make_tuple( std::get<As>(std::forward<T>(t))..., std::get<N+1+Bs>(std::forward<T>(t))... );
    } );
  });
}

Live example.

This avoids depth-N template instantiation recursion of template instantiation "volume" N^2, which is hard to do in C++11 for this problem.

indexer is a cute little helper that lets us expand counting parameter packs without having to write a new function.

indexer<N>()( [&]( auto...Is ) { /* code */ } ) gives us the integers 0 through N-1 in a pack Is... within /* code */.

We do this twice, once for the leading elements, once for the tailing. Then we wrap it all up in a single make_tuple call.

Note that gcc in mode needs

      return std::make_tuple( std::get<decltype(As){}>(std::forward<T>(t))..., std::get<N+1+decltype(Bs){}>(std::forward<T>(t))... );

as it won't let you call a member function with a non-constexpr object even if the function does not use this. clang works fine, even in . I think this was a standard ambiguity that was cleared up later.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • I changed the compiler in the Live Example to g++ and the code didn't build anymore. I like the idea of this solution, and am trying to get it compiled in the compiler I use. – Candy Chiu Sep 01 '20 at 15:09
  • I have a question on the expansion. Let's take after as example. It takes a functions f, and expands the index pack into its arguments -- f (0,1,2). std::get(tuple) takes these indices as template arguments, not function arguments. Would you explain how f ( std::integral_constant<>… ) ties to std::get()? – Candy Chiu Sep 01 '20 at 15:20
  • @candy replace `get` with `get` and similar for `Bs` to get it to work. It is complaining about a constexpr operator size_t and gcc c++14 mode is ststing it isn't constexpr, because object isn't. (Operator doesn't use this, so clang considers it constexpr). Later standard made it clear clang is right iirc. – Yakk - Adam Nevraumont Sep 01 '20 at 15:25
  • @candy it does `f( 0_k, 1_k, ..., {n-1}_k )` where `x_k` is an integral_constant that encodes the value x in its type. These can be converted to integers as constexpr even if `*this` isn't constexpr (but not in gcc c++14 mode). `indexer<3>( f )` calls `f(0_k,1_k,2_k)`. We then call `indexer` in `f`, and pass it `g`, which gets `0...size-1-3` as constants. We then expand both packs, adding N+1 to second, getting tuple element `0,1,2` then `4,5,6,7`. Which we pass to make tuple, and return all the way out. – Yakk - Adam Nevraumont Sep 02 '20 at 03:42
0
#include <iostream>
#include <string>
#include <tuple>
#include <cassert>
#include <utility>

namespace details
{
    template <std::size_t N, std::size_t Except, std::size_t... Next>
    struct index_sequence_helper : public index_sequence_helper<N - 1U, Except, N - 1U, Next...>
    {
    };

    template <std::size_t N, std::size_t... Next>
    struct index_sequence_helper<N, N, Next...> : public index_sequence_helper<N - 1U, N, Next...>
    {
    };

    template <std::size_t Except, std::size_t... Next>
    struct index_sequence_helper<0U, Except, Next...>
    {
        using type = std::index_sequence<Next...>;
    };
} // namespace details

template <std::size_t N, std::size_t Except>
using make_index_sequence_except = typename details::index_sequence_helper<N, Except + 1>::type;

template <class Tuple, std::size_t... I>
auto make_tuple_by_seq(const Tuple &t, std::index_sequence<I...>)
{
    return std::make_tuple(std::get<I>(t)...);
}

template <size_t N, class... T>
auto expand_except_ith(const std::tuple<T...> &t)
{
    return make_tuple_by_seq(t, make_index_sequence_except<sizeof...(T), N>{});
}

int main()
{
    std::tuple<int, double, std::string> t;
    // some facility to expand except the i-th type:
    auto t0 = expand_except_ith<0>(t); // returns tuple<double, string>
    auto t1 = expand_except_ith<1>(t); // returns tuple<int, string>
    auto t2 = expand_except_ith<2>(t); // returns tuple<int, double>

    std::cout << std::is_same_v<decltype(t0), std::tuple<double, std::string>> << std::endl;
    std::cout << std::is_same_v<decltype(t1), std::tuple<int, std::string>> << std::endl;
    std::cout << std::is_same_v<decltype(t2), std::tuple<int, double>> << std::endl;
}

I modified the code from details of std::make_index_sequence and std::index_sequence

Hongxu Xu
  • 63
  • 1
  • 7
0

Here is a short solution using Boost.MP11 to erase the single type denoted by an index in the type holder class (e.g. std::tuple, boost::mp11::mp_list, etc.).

#include <tuple>
#include <string>

#include <boost/mp11/algorithm.hpp>

template<auto N, template<class...> class TList, class... Ts>
constexpr auto expand_except_ith(TList<Ts...>) {
    static_assert(N <= sizeof...(Ts), "Index to skip must be within range of size of types");
    return boost::mp11::mp_erase_c<TList<Ts...>, N, N+1>{};
}


int main() {
    std::tuple<int, double, std::string> t;
    static_assert(std::is_same_v<std::tuple<double, std::string>, decltype(expand_except_ith<0>(t))>);  
    static_assert(std::is_same_v<std::tuple<int, std::string>, decltype(expand_except_ith<1>(t))>);
    static_assert(std::is_same_v<std::tuple<int, double>, decltype(expand_except_ith<2>(t))>); 
}
Joe
  • 171
  • 1
  • 3