将 std::array<T,N> 解包为函数参数

3

这里有一个非常好的例子(不是我的),展示了如何将元组作为参数传递到函数中:

template<int ...I> struct index_tuple_type {
  template<int N> using append = index_tuple_type<I..., N>;
};

template<int N> struct make_index_impl {
  using type = typename make_index_impl<N-1>::type::template append<N-1>;
};

template<> struct make_index_impl<0> { using type = index_tuple_type<>; };

template<int N> using index_tuple = typename make_index_impl<N>::type;

template <typename I, typename ...Args>
struct func_traits;

template <typename R, int ...I, typename ...Args>
struct func_traits<R, index_tuple_type<I...>, Args...> {
  template <typename TT, typename FT>
  static inline R call(TT &&t, FT &&f) {
    return f(std::get<I>(std::forward<TT>(t))...);
  }
};

template<
  typename FT,
  typename ...Args, 
  typename R = typename std::result_of<FT(Args&&...)>::type
>
inline R explode(std::tuple<Args...>& t, FT &&f) {
  return func_traits<R, index_tuple<sizeof...(Args)>, Args...>
    ::call(t, std::forward<FT>(f));
}

然后你可以这样使用它:
void test1(int i, char c) {
  printf("%d %c\n", i, c);
}

int main() {
  std::tuple<int, char> t1{57, 'a'};
  explode(t1, test1);
}

实时版本

我在想,如何使用std::array完成类似的操作,因为它与元组很相似。std::get<N>可用于std::array,所以我认为很容易修改此解决方案。但是像这样的代码并不起作用:

template<
  typename FT,
  typename Arg,
  std::size_t I,
  typename R = typename std::result_of<FT(Arg&&)>::type
>
inline R explode(std::array<Arg, I>& t, FT &&f) {
  return func_traits<R, index_tuple<I>, Arg>::
    call(t, std::forward<FT>(f));
}

void test2(int i1, int i2) {
  printf("%d %d\n", i1, i2);
}

int main() {
  std::array<int, int> t1{1, 2};
  explode(t2, test1);
}

因为部分 std::result_of<FT(Arg&&)>::type,参数类型Arg&&是错误的,并且result_of没有字段type。对于元组Args&&...已经扩展,但现在应该“重复”I次。是否有一种使用result_of来完成这个过程,以便可以推断返回类型的方法?
另外,我想知道,如果有“解包”tuplearray的工具,是否有可能递归“解包”像tuple<array<int, 2>, tuple<array<double,3>, ...这样的结构,等等?一种类似树形的结构,其中tuplearray是分支,其他类型则是叶子?

1
std::forward中的左值引用硬编码为类型模板参数等同于“无操作”,你可能想把t作为它的参数。另外,使用尾返回类型 - Piotr Skotnicki
你是否考虑过编写一个将数组转换为引用元组的函数? - user1084944
@PiotrS. 更正了我的错误,我错误地扩展了宏。@Hurkyl 是的,我考虑过,但这不是一个有点绕的方法吗? - The_Ham
1
@The_Ham,它仍然不能按照你的想法工作。现在std::forward<std::tuple<Args...>>(t)等同于std::move(t)(无条件)。 - Piotr Skotnicki
将一堆类似于“元组”的内容展平成一个单独的“元组”,在我看来是一个不同的问题。我回答了你的第一个问题。 - Yakk - Adam Nevraumont
由于没有人提到,建议使用 apply 可以像 std::tuple 一样与 std::array 一起使用。在此处查看实例 - DanielKO
1个回答

8
// enable argument dependent lookup on `get` call:
namespace aux {
  using std::get;
  template<size_t N, class T>
  auto adl_get( T&& )->decltype( get<N>(std::declval<T>()) );
}
using aux::adl_get;
template<class F, class TupleLike, size_t...Is>
auto explode( F&& f, TupleLike&& tup, std::index_sequence<Is...> )
-> std::result_of_t< F( decltype(adl_get<Is>(std::forward<TupleLike>(tup)))... ) >
{
  using std::get; // ADL support
  return std::forward<F>(f)( get<Is>(std::forward<TupleLike>(tup))... );
}

第一步是实现std::index_sequence,它是C++14的特性,但在C++11中也很容易实现。

接下来的步骤也很简单。

首先,我们需要一个traits类来指定哪些类型类似于元组。虽然我会直接使用duck-typing,但我们要使用的许多函数和traits类不支持SFINAE:

template<class T>
struct tuple_like:std::false_type{};
template<class... Ts>
struct tuple_like<std::tuple<Ts...>>:std::true_type{};
template<class... Ts>
struct tuple_like<std::pair<Ts...>>:std::true_type{};
template<class T, size_t N>
struct tuple_like<std::array<T,N>>:std::true_type{};

接下来是一个仅适用于 tuple_like 类型的 explode 过载:

template<class F, class TupleLike,
  class TupleType=std::decay_t<TupleLike>, // helper type
  class=std::enable_if_t<tuple_like<TupleType>{}>> // SFINAE tuple_like test
auto explode( F&& f, TupleLike&& tup )
-> decltype(
  explode(
    std::declval<F>(),
    std::declval<TupleLike>(), 
    std::make_index_sequence<std::tuple_size<TupleType>{}>{}
  )
)
{
   using indexes = std::make_index_sequence<std::tuple_size<TupleType>{}>;
   return explode(
     std::forward<F>(f),
     std::forward<TupleLike>(tup),
     indexes{}
   );
}

如果您缺少constexpr支持,您需要将一些{}更改为::value
上述代码可以处理成对、数组或元组。如果要为其他类似于元组的类型添加支持,只需在tuple_like中添加一个专门化,并确保为您的类型正确地专门化了std::tuple_sizeget<N>被ADL重载(在类型的封闭命名空间中)。
std::make_index_sequence也是C++14,但在C++11中很容易编写。
template<size_t...>
struct index_sequence{};
namespace details {
  template<size_t count, size_t...Is>
  struct mis_helper:mis_helper<count-1, count-1, Is...> {};
  template<size_t...Is>
  struct mis_helper<0,Is...> {
    using type=index_sequence<Is...>;
  };
}
template<size_t count>
using make_index_sequence=typename details::mis_helper<count>::type;

这是一个质量较差的C++14库,应至少使用对数下降,因为它需要对大小为n的列表进行O(n)的模板递归模板实例化。然而,如果n小于几百个,那就无所谓了。

std::enable_if_t<?>是C++14的写法,但在C++11中只需使用typename std::enable_if<?>::type


1
为什么要重载而不是使用“TupleLike”并使用“tuple_size”? - T.C.
1
@T.C. 当tuple_size被传递的不是元组/数组/对时,其未指定的行为意味着它不能进行清晰的SFINAE。而根据我的经验,这种情况的用例受益于SFINAE。 - Yakk - Adam Nevraumont
1
@The_Ham 一个可能可行的方法。创建struct tuple_flattener,它接受其参数,并对于每个参数,如果它们是元组,则执行explode(tuple_flattener {},arg),否则只需执行arg。然后,它将结果make_tuple并返回。现在,调用explode(f,tuple_flattener{}(arg))以获得您的扁平爆炸。我认为这会起作用。(此评论的早期版本未能递归足够)。推断返回类型仍然很棘手,您可能需要独立完成而不依赖于auto - Yakk - Adam Nevraumont
@Yakk 抱歉,我没能及时看到那条评论。谢谢你的帮助。我可以问一下你是从哪里获取关于这个C++11元编程的知识的吗?这让我头疼不已,更别说自己写出什么有意义的东西了。 - The_Ham
@t.c 不知道。有时候我会收到没有 @ 地址的消息? - Yakk - Adam Nevraumont
显示剩余4条评论

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