将std::tuple转换为std::array C++11

22
如果我有一个类型相同的std::tuple<double, double, double>,是否有现成的函数或者构造函数可以将其转换为std::array<double>
编辑:我已经通过递归模板代码解决了这个问题(我的草稿答案在下面)。这是处理此问题的最佳方法吗?看起来应该有现成的功能可以实现...如果您对我的答案有改进意见,我将不胜感激。我会保留未回答的问题(毕竟,我想要的是一种好的方式,而不仅仅是可行的方式),并且更希望选择其他人的[更好的]答案。
谢谢您的建议。

1
你说得对,有一种方法可以通常解包元组而不使用递归(这对于你的问题有效)。但是我们没有一个通用的答案来概述该技术。无论如何,我为你写了一个例子。 - Luc Danton
如果您使用像std::common_type这样的东西,则可以取消同构元组要求。在将它们传递给common_type之前,请剥离每个元组元素类型的引用和cv限定符部分。 - CTMacUser
7个回答

27

将元组转换为数组,不使用递归,并包括使用完美转发(适用于仅可移动类型):

#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;
}

2
+1 索引数组正是我正在寻找的东西——一种类似于“for each”的std::tuple框架。我对你的答案提出了几个修改建议(我在使用g++-4.7 -std=c++11编译时修复了一些编译错误,并添加了一个main函数)。非常感谢你的回答。 - user
@Luc - 这是非常难以理解的代码。我会感激一些解释。一个简单的问题:为什么你使用std :: declval <Tuple>()而不是简单地使用tuple? - Uri London
@Uri Tuple 可能无法进行默认构造,此时 Tuple {} 会产生错误,但 std::declval<Tuple>() 不会。我建议你查找有关 std::declval 的解释——在这种通用代码中,它的使用频繁,但并不是实际功能的关键。 - Luc Danton
3
现在有一个 std::make_index_sequence 可以使这项工作更容易。也许值得使用它来更新这个答案。 - Andrew Tomazos
@LucDanton,请注意我已经将您的代码提取到这里的答案中:http://stackoverflow.com/questions/33955275/initializing-c-stdarray-with-smaller-arrays 我在答案中给了您的功劳。 - Richard Hodges

13

C++17的解决方案很简短:

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));
}

使用它作为

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

编辑:忘记在任何地方使用 constexpr 了 :-)


9

你可以不使用递归来完成它:

#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());
  }

请查看https://gitlab.com/redistd/redistd/blob/master/include/redi/index_tuple.h,这是我的index_tuple实现,类似的工具对于处理元组和类似的可变参数模板很有用。C++14标准化了一个类似的工具std::index_sequence(在index_seq.h中可以找到独立的C++11实现)。


7

我建议返回数组而不是通过引用填充它,这样就可以使用auto使调用更加简洁:

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

实际上,命名返回值优化 (NRVO) 将消除大多数性能问题。

+1 关于 NRVO 的好点子。我有时候会忘记编译器是那么聪明。 - user

4

即使标题写着C++11,我认为C++14的解决方案也值得分享(因为每个搜索这个问题的人都会来到这里)。这个解决方案可以在编译时使用(constexpr合适),而且比其他解决方案要短得多。

#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;
}

示例


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;
}

1
在C++14中,您可以执行以下操作生成一个数组:
auto arr = apply([](auto... n){return std::array<double, sizeof...(n)>{n...};}, tup);

完整代码:

#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 );

}

请注意,微妙的问题是当源元组中的所有类型不同时该怎么办,应该编译失败吗?使用隐式转换规则?使用显式转换规则?

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接