【问题标题】:"unpack" std::array<T,N> as arguments to function“解包” std::array<T,N> 作为函数的参数
【发布时间】:2015-02-10 19:29:28
【问题描述】:

这是一个很好的(不是我的)例子,你可以如何扩展(或“分解”)元组作为函数的参数:

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

Live version

我在想你怎么能用std::array 做同样的事情,因为它很像元组。 std::get&lt;N&gt;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&lt;FT(Arg&amp;&amp;)&gt;::type 部分。参数类型Arg&amp;&amp; 错误,result_of 没有字段type。对于元组Args&amp;&amp;... 扩展,但现在应该“重复”I 次。有没有办法使用result_of 做到这一点,这样可以扣除返回的类型?

我还想知道,拥有“解包”tuplearray 的工具是否可以递归地“解包”(可能使用enable_if)结构,如tuple&lt;array&lt;int, 2&gt;, tuple&lt;array&lt;double,3&gt;, ... 等等?某种树,其中tuplearray 是树枝,而其他类型是树叶?

【问题讨论】:

  • std::forward 将左值引用编码为类型模板参数等于“无操作”。你可能打算把t 作为它的论点。顺便说一句,请改用trailing return type like here
  • 您是否考虑过创建一个将数组转换为引用元组的函数?
  • @PiotrS。更正了forward,我的错误扩展宏以错误的方式进行。 @Hurkyl 是的,我想过,但这不是一点点。
  • @The_Ham 它仍然不会像你想象的那样工作。现在std::forward&lt;std::tuple&lt;Args...&gt;&gt;(t) 等于std::move(t)(无条件)
  • 在我看来,将一堆 tuple-likes 扁平化为一个 tuple 是一个独特的问题。我回答了你的第一个问题。

标签: c++ templates c++11 stdarray stdtuple


【解决方案1】:
// 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 中很容易实现。

接下来的步骤也很简单。

首先,一个特征类,它规定了哪些类型是类似元组的。我会继续使用它们,只是鸭式使用它们,但是我们将使用的一些函数和特征类对 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{};

接下来,explode 的重载仅适用于 tuple_like 类型:

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_size 适合您的类型并且get&lt;N&gt; 是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 库来说 QOI 很差,它应该至少使用对数下降,因为它需要 O(n) 模板递归模板实例化来处理大小为 n 的列表。但是,n 是否小于几百个,没关系)。

std::enable_if_t&lt;?&gt; 是 C++14,但在 C++11 中只是 typename std::enable_if&lt;?&gt;::type

【讨论】:

  • 为什么要重载而不是只取TupleLike 并使用tuple_size
  • @T.C.当tuple_size 被交给不是元组/数组/对的东西时,未指定的行为意味着它不会执行干净的 SFINAE。根据我的经验,这种事情的用例受益于 SFINAE。
  • @T.C.那里。 SFINAE 检查tuple_likeness,一个转发TupleType
  • @The_Ham 没有。我在您的问题下方的评论表明您问了两个问题:我回答了一个。扁平化可能类似于元组的内容的类似元组类型的问题与使用一组类似元组的参数调用函数的问题不同。在堆栈溢出时,您应该在每个帖子中问一个问题。用(用元组调用函数)和获取(用扁平元组调用函数)很容易组合(扁平元组);但是直接编写(调用带有扁平元组的函数)是个坏主意。
  • @The_Ham 一种可能有效的方法。创建带有参数的struct tuple_flattener,对于每个它们都是元组,执行explode( tuple_flattener{}, arg ),而不是arg。然后它make_tuples 结果并返回它。现在,拨打explode( f, tuple_flattener{}(arg) ) 来获得你的扁平化爆炸。我认为这会奏效。 (此评论的早期版本未能足够递归)。推断返回类型仍然很棘手,您可能必须独立完成,而不是依赖auto
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-21
  • 1970-01-01
  • 2020-08-08
  • 1970-01-01
  • 2016-05-31
  • 1970-01-01
相关资源
最近更新 更多