How do you convert a homogeneous std::tuple to a std::array?
Asked Answered
N

7

24

If I have std::tuple<double, double, double, ...> (where the types are homogeneous), is there a stock function or constructor to convert to std::array<double, N>?

I was able to get it working with recursive template code (my draft answer posted below), but I would prefer someone else's, hopefully better solution.

Nne answered 15/5, 2012 at 16:13 Comment(2)
You're right, there is a way to generally unpack tuples without making use of recursion (which would work for your problem). However we don't have a generic answer that outlines the technique. In any case I wrote you an example.Gaudery
If you use something like std::common_type, you could drop the homogeneous-tuple requirement. Strip the reference and cv-qualifier parts of each tuple element type before passing them to common_type.Fsh
G
27

Converting a tuple to an array without making use of recursion, including use of perfect-forwarding (useful for move-only types):

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

template<int... Indices>
struct indices {
    using next = indices<Indices..., sizeof...(Indices)>;
};

template<int Size>
struct build_indices {
    using type = typename build_indices<Size - 1>::type::next;
};

template<>
struct build_indices<0> {
    using type = indices<>;
};

template<typename T>
using Bare = typename std::remove_cv<typename std::remove_reference<T>::type>::type;

template<typename Tuple>
constexpr
typename build_indices<std::tuple_size<Bare<Tuple>>::value>::type
make_indices()
{ return {}; }

template<typename Tuple, int... Indices>
std::array<
  typename std::tuple_element<0, Bare<Tuple>>::type,
    std::tuple_size<Bare<Tuple>>::value
>
to_array(Tuple&& tuple, indices<Indices...>)
{
    using std::get;
    return {{ get<Indices>(std::forward<Tuple>(tuple))... }};
}

template<typename Tuple>
auto to_array(Tuple&& tuple)
-> decltype( to_array(std::declval<Tuple>(), make_indices<Tuple>()) )
{
    return to_array(std::forward<Tuple>(tuple), make_indices<Tuple>());
}

int main() {
  std::tuple<double, double, double> tup(1.5, 2.5, 4.5);
  auto arr = to_array(tup);
  for (double x : arr)
    std::cout << x << " ";
  std::cout << std::endl;
  return 0;
}
Gaudery answered 16/5, 2012 at 8:55 Comment(5)
+1 Index arrays are exactly the kind of thing that I am looking for-- a sort of "for each" framework for std::tuple. I proposed a couple edits to your answer (I fixed a few compilation errors when compiled on with g++-4.7 -std=c++11 and added a main). Thanks a lot for the answer.Nne
@Luc - this is very hard code to decipher. I would appreciate some explanation. one easy question: why are you using std::declval<Tuple>(), instead of simply tuple?Constantine
@Uri The Tuple may not be default constructible, in which case Tuple {} is an error but std::declval<Tuple>() isn't. I suggest you look around for explanations about std::declval -- its use is frequent in this kind of generic code, but isn't really crucial to the actual functionality present here.Gaudery
There is now a std::make_index_sequence that makes this job a little easier. It might be worth updating this answer using it.Flexure
@LucDanton Please be advised that I have lifted your code into an answer here: #33955775 I have given you credit in the answer.Magdala
U
16

The C++17 solution is a short one:

template<typename tuple_t>
constexpr auto get_array_from_tuple(tuple_t&& tuple)
{
    constexpr auto get_array = [](auto&& ... x){ return std::array{std::forward<decltype(x)>(x) ... }; };
    return std::apply(get_array, std::forward<tuple_t>(tuple));
}

Use it as

auto tup = std::make_tuple(1.0,2.0,3.0);
auto arr = get_array_from_tuple(tup);

EDIT: forgot to sprinkle constexpr anywhere :-)

Unipolar answered 1/2, 2019 at 20:54 Comment(0)
P
9

You can do it non-recursively:

#include <array>
#include <tuple>
#include <redi/index_tuple.h>  // see below

template<typename T, typename... U>
  using Array = std::array<T, 1+sizeof...(U)>;

template<typename T, typename... U, unsigned... I>
  inline Array<T, U...>
  tuple_to_array2(const std::tuple<T, U...>& t, redi::index_tuple<I...>)
  {
    return Array<T, U...>{ std::get<I>(t)... };
  }

template<typename T, typename... U>
  inline Array<T, U...>
  tuple_to_array(const std::tuple<T, U...>& t)
  {
    using IndexTuple = typename redi::make_index_tuple<1+sizeof...(U)>::type;
    return tuple_to_array2(t, IndexTuple());
  }

See https://gitlab.com/redistd/redistd/blob/master/include/redi/index_tuple.h for my implementation of index_tuple, something like that is useful for working with tuples and similar variadic templates. A similar utility was standardised as std::index_sequence in C++14 (see index_seq.h for a standalone C++11 implementation).

Phthisic answered 16/5, 2012 at 15:0 Comment(0)
E
7

I would return the array instead of populating it by reference, so that auto can be used to make the callsite cleaner:

template<typename First, typename... Rem>
std::array<First, 1+sizeof...(Rem)>
fill_array_from_tuple(const std::tuple<First, Rem...>& t) {
  std::array<First, 1+sizeof...(Rem)> arr;
  ArrayFiller<First, decltype(t), 1+sizeof...(Rem)>::fill_array_from_tuple(t, arr);
  return arr;
}

// ...

std::tuple<double, double, double> tup(0.1, 0.2, 0.3);
auto arr = fill_array_from_tuple(tup);

Realistically, NRVO will eliminate most performance concerns.

Execrate answered 15/5, 2012 at 20:52 Comment(1)
+1 Good point re NRVO. I sometimes forget that compilers are that smart.Nne
N
4
#include <iostream>
#include <tuple>
#include <array>

template<class First, class Tuple, std::size_t N, std::size_t K = N>
struct ArrayFiller {
  static void fill_array_from_tuple(const Tuple& t, std::array<First, N> & arr) {
    ArrayFiller<First, Tuple, N, K-1>::fill_array_from_tuple(t, arr);
    arr[K-1] = std::get<K-1>(t);
  }
};

template<class First, class Tuple, std::size_t N>
struct ArrayFiller<First, Tuple, N, 1> {
  static void fill_array_from_tuple( const Tuple& t, std::array<First, N> & arr) {
    arr[0] = std::get<0>(t);
  }
};

template<typename First, typename... Rem>
void fill_array_from_tuple(const std::tuple<First, Rem...>& t, std::array<First, 1+sizeof...(Rem)> & arr) {
  ArrayFiller<First, decltype(t), 1+sizeof...(Rem)>::fill_array_from_tuple(t, arr);
}

int main() {
  std::tuple<double, double, double> tup(0.1, 0.2, 0.3);
  std::array<double, 3> arr;
  fill_array_from_tuple(tup, arr);

  for (double x : arr)
    std::cout << x << " ";
  return 0;
}
Nne answered 15/5, 2012 at 17:53 Comment(0)
D
4

Even if title says C++11 I think C++14 solution is worth sharing (since everyone searching for problem will come up here anyway). This one can be used in compile time (constexpr proper) and much shorter than other solutions.

#include <array>
#include <tuple>
#include <utility>
#include <iostream>

// Convert tuple into a array implementation
template<typename T, std::size_t N, typename Tuple,  std::size_t... I>
constexpr decltype(auto) t2a_impl(const Tuple& a, std::index_sequence<I...>)
{
        return std::array<T,N>{std::get<I>(a)...};
}

// Convert tuple into a array
template<typename Head, typename... T>
constexpr decltype(auto) t2a(const std::tuple<Head, T...>& a)
{
        using Tuple = std::tuple<Head, T...>;
        constexpr auto N = sizeof...(T) + 1;
        return t2a_impl<Head, N, Tuple>(a, std::make_index_sequence<N>());
}

int main()
{
    constexpr auto tuple = std::make_tuple(-1.3,2.1,3.5);
    constexpr auto array = t2a(tuple);

    static_assert(array.size() == 3, "err");

    for(auto k : array)
        std::cout << k << ' ';
    return 0;
}

Example

Disburden answered 11/1, 2018 at 10:29 Comment(0)
P
1

In C++14 you can do this to generate an array:

auto arr = apply([](auto... n){return std::array<double, sizeof...(n)>{n...};}, tup);

Full code:

#include<experimental/tuple> // apply
#include<cassert>

//using std::experimental::apply; c++14 + experimental
using std::apply; // c++17

template<class T, class Tuple>
auto to_array(Tuple&& t){
    return apply([](auto... n){return std::array<T, sizeof...(n)>{n...};}, t); // c++14 + exp
}

int main(){
    std::tuple<int, int, int> t{2, 3, 4};

    auto a = apply([](auto... n){return std::array<int, 3>{n...};}, t); // c++14 + exp
    assert( a[1] == 3 );

    auto a2 = apply([](auto... n){return std::array<int, sizeof...(n)>{n...};}, t); // c++14 + exp
    assert( a2[1] == 3 );

    auto a3 = apply([](auto... n){return std::array{n...};}, t); // c++17
    assert( a3[1] == 3 );

    auto a4 = to_array<int>(t);
    assert( a4[1] == 3 );

}

Note that the subtle problem is what to do when all types in the source tuple are not the same, should it fail to compile? use implicit conversion rules? use explicit conversion rules?

Pourparler answered 22/11, 2019 at 7:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.