How to flatten heterogeneous lists (aka tuples of tuples of ...)

namespace flattenns {
  struct flat_t {};

  template<std::size_t... Is, class...As>
  constexpr auto flatten( std::index_sequence<Is...>, flat_t, std::tuple<As...> as ) {
    return std::tuple_cat( flatten(flat_t{}, std::get<Is>(as))... );
  }
  template<class...As, class...Ts>
  constexpr auto flatten( flat_t, std::tuple<As...> as ) {
    return flatten( std::make_index_sequence<sizeof...(As)>{}, flat_t{}, as );
  }
  template<class T>
  constexpr std::tuple<T> flatten( flat_t, T t ) { return {t}; }

  template<class...Ts>
  constexpr auto flatten( flat_t, Ts... ts ) {
    return std::tuple_cat( flatten(flat_t{}, ts)... );
  }
  constexpr std::tuple<> flatten( flat_t ) { return {}; }
}
template<class...Ts>
constexpr auto sane_flatten( Ts...ts ) {
  return flattenns::flatten(flattenns::flat_t{}, ts...);
}

// to take std::tuple<int>(7) -> 7
namespace insanens {
    template<class...Ts>
    constexpr auto unpack_single( std::tuple<Ts...> t ) {return t;}
    template<class T>
    constexpr auto unpack_single( std::tuple<T> t ) {return std::get<0>(t);}
}
template<class...Ts>
constexpr auto insane_flatten( Ts...ts ) {
  return insanens::unpack_single( sane_flatten(ts...) );
}
template<class...Ts>
constexpr auto flatten( Ts...ts ) {
    return insane_flatten(ts...);
}

As noted above, flatten( std::tuple<int>(7) ) should NOT BE 7. That is insanity.

But as you want it, I add it as a post-processing step.

Your operation is otherwise relatively sane. You are recursively applying [[x],[y]] to [x,y]. The final unboxing is not sane. By splitting it off, the code becomes easy, which is also evidence why it is insane.

Live example.

In case you are wondering, the flat_t tag type exists in order to (a) split the index sequence from a possible argument (which could be done by having a different function name) and (b) enable ADL lookup so every implementation of flatten can see all of the other ones.


I propose to SFINAE on presence of tuple

// Simple traits
template <typename T> struct is_tuple : std::false_type{};
template <typename... Ts> struct is_tuple<std::tuple<Ts...>> : std::true_type{};

// utility to ensure return type is a tuple
template<typename T>
constexpr decltype(auto) as_tuple(T t) { return std::make_tuple(t); }

template<typename ...Ts>
constexpr decltype(auto) as_tuple(std::tuple<Ts...> t) { return t; }

// Simple case
template<typename T>
constexpr decltype(auto) flatten(T t)
{
    return t;
}

// Possibly recursive tuple
template<typename T>
constexpr decltype(auto) flatten(std::tuple<T> t)
{
    return flatten(std::get<0>(t));
}

// No more recursion, (sizeof...Ts != 1) with above overload
template<typename ...Ts, std::enable_if_t<!(is_tuple<Ts>::value || ...), bool> = false>
constexpr decltype(auto) flatten(std::tuple<Ts...> t)
{
    return t;
}

// Handle recursion
template<typename ...Ts, std::enable_if_t<(is_tuple<Ts>::value || ...), bool> = false>
constexpr decltype(auto) flatten(std::tuple<Ts...> t)
{
    return std::apply([](auto...ts)
                      {
                          return flatten(std::tuple_cat(as_tuple(flatten(ts))...));
                      }, t);
}

Demo