21

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.

4

7 回答 7

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;
}
于 2012-05-16T08:55:51.223 回答
11

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

于 2019-02-01T20:54:43.317 回答
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).

于 2012-05-16T15:00:08.987 回答
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.

于 2012-05-15T20:52:55.990 回答
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;
}
于 2012-05-15T17:53:03.960 回答
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

于 2018-01-11T10:29:14.343 回答
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?

于 2019-11-22T07:51:26.490 回答