使用 std::get、std::tuple_size、std::tuple_element 对元组的组件求和

Sum the components of a tuple up by using std::get, std::tuple_size, std::tuple_element

我有一个自定义 class,它具有类似元组的界面。因为我希望我的代码尽可能通用,所以我认为将我的算法基于函数 std::getstd::tuple_sizestd::tuple_element 是个好主意,所以您只需要专门化这些功能以使用我的算法。我们将需要这些函数特化的概念称为 Tuple.

现在我正在尝试总结 Tuple 的组成部分。函数声明应该是这样的:

template <class Tuple>
int sum_components(const Tuple& t);

我想这涉及到很多模板编程,但我不知道如何去做。

对于添加,我将只使用全局 + operator.

的重载

我正在使用 c++1z。

对于 C++1z,fold expressions 非常简单。首先,将元组转发给一个 _impl 函数并为其提供索引序列以访问所有元组元素,然后求和:

template<typename T, size_t... Is>
auto sum_components_impl(T const& t, std::index_sequence<Is...>)
{
    return (std::get<Is>(t) + ...);
}

template <class Tuple>
int sum_components(const Tuple& t)
{
    constexpr auto size = std::tuple_size<Tuple>{};
    return sum_components_impl(t, std::make_index_sequence<size>{});
}

demo


C++14 方法是递归地对可变参数包求和:

int sum()
{
    return 0;
}

template<typename T, typename... Us>
auto sum(T&& t, Us&&... us)
{
    return std::forward<T>(t) + sum(std::forward<Us>(us)...);
}

template<typename T, size_t... Is>
auto sum_components_impl(T const& t, std::index_sequence<Is...>)
{
    return sum(std::get<Is>(t)...);
}

template <class Tuple>
int sum_components(const Tuple& t)
{
    constexpr auto size = std::tuple_size<Tuple>{};
    return sum_components_impl(t, std::make_index_sequence<size>{});
}

demo

C++11 方法是自定义实现 index_sequence 的 C++14 方法。例如来自 here.


正如@ildjarn 在评论中指出的那样,上面的示例都使用了右折叠,而许多程序员希望在他们的代码中使用左折叠。 C++1z 版本的变化很小:

template<typename T, size_t... Is>
auto sum_components_impl(T const& t, std::index_sequence<Is...>)
{
    return (... + std::get<Is>(t));
}

demo

C++14 并没有差多少,但有更多变化:

template<typename T, typename... Us>
auto sum(T&& t, Us&&... us)
{
    return sum(std::forward<Us>(us)...) + std::forward<T>(t);
}

template<typename T, size_t... Is>
auto sum_components_impl(T const& t, std::index_sequence<Is...>)
{
    constexpr auto last_index = sizeof...(Is) - 1;
    return sum(std::get<last_index - Is>(t)...);
}

demo

这在 中非常容易。

template<class Tuple>
decltype(auto) sum_components(Tuple const& tuple) {
  auto sum_them = [](auto const&... e)->decltype(auto) {
    return (e+...);
  };
  return std::apply( sum_them, tuple );
};

(...+e)为相反的折叠方向。

在以前的版本中,正确的方法是编写您自己的 apply 而不是编写定制的实现。当您的编译器更新时,您可以删除代码。

,我可能会这样做:

// namespace for utility code:
namespace utility {
  template<std::size_t...Is>
  auto index_over( std::index_sequence<Is...> ) {
    return [](auto&&f)->decltype(auto){
      return decltype(f)(f)( std::integral_constant<std::size_t,Is>{}... );
    };
  }
  template<std::size_t N>
  auto index_upto() {
    return index_over( std::make_index_sequence<N>{} );
  }
}
// namespace for semantic-equivalent replacements of `std` code:
namespace notstd {
  template<class F, class Tuple>
  decltype(auto) apply( F&& f, Tuple&& tuple ) {
    using dTuple = std::decay_t<Tuple>;
    auto index = ::utility::index_upto< std::tuple_size<dTuple>{} >();
    return index( [&](auto...Is)->decltype(auto){
      auto target=std::ref(f);
      return target( std::get<Is>( std::forward<Tuple>(tuple) )... );
    } ); 
  }
}

中的 std::apply 非常接近。 (我滥用 std::ref 来获得 INVOKE 语义)。 (它不能与右值调用器完美配合,但这是非常极端的情况)。

, I would advise upgrading your compiler at this point. In 我建议此时升级你的工作。


以上所有的都做右折或左折。在某些情况下,二叉树折叠可能会更好。这比较棘手。

如果您的 + 使用表达式模板,由于生命周期问题,上述代码将无法正常工作。在某些情况下,您可能必须为 "afterwards, cast-to" 添加另一个模板类型才能使临时表达式树求值。