22

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

Edit:: I was able to get it working with recursive template code (my draft answer posted below). Is this the best way to handle this? It seems like there would be a stock function for this... Or if you have improvements to my answer, I'd appreciate it. I'll leave the question unanswered (after all, I want a good way, not just a workable way), and would prefer to select someone else's [hopefully better] answer.

Thanks for your advice.

ildjarn
  • 62,044
  • 9
  • 127
  • 211
user
  • 7,123
  • 7
  • 48
  • 90
  • 1
    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](http://stackoverflow.com/questions/7858817/unpacking-a-tuple-to-call-a-matching-function-pointer/7858971#7858971). In any case I wrote you an example. – Luc Danton May 16 '12 at 08:58
  • 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`. – CTMacUser Apr 29 '13 at 09:39

7 Answers7

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;
}
user
  • 7,123
  • 7
  • 48
  • 90
Luc Danton
  • 34,649
  • 6
  • 70
  • 114
  • 2
    +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. – user May 20 '12 at 17:29
  • @Luc - this is very hard code to decipher. I would appreciate some explanation. one easy question: why are you using std::declval(), instead of simply tuple? – Uri London Oct 16 '12 at 16:31
  • @Uri The `Tuple` may not be default constructible, in which case `Tuple {}` is an error but `std::declval()` 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. – Luc Danton Oct 16 '12 at 21:46
  • 3
    There is now a `std::make_index_sequence` that makes this job a little easier. It might be worth updating this answer using it. – Andrew Tomazos May 17 '14 at 05:26
  • @LucDanton Please be advised that I have lifted your code into an answer here: http://stackoverflow.com/questions/33955275/initializing-c-stdarray-with-smaller-arrays I have given you credit in the answer. – Richard Hodges Nov 27 '15 at 11:20
13

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 :-)

davidhigh
  • 14,652
  • 2
  • 44
  • 75
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).

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
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.

ildjarn
  • 62,044
  • 9
  • 127
  • 211
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;
}
user
  • 7,123
  • 7
  • 48
  • 90
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

R2RT
  • 2,061
  • 15
  • 25
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?

alfC
  • 14,261
  • 4
  • 67
  • 118