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

4
我有一个类,它具有类似于元组的接口。为了使我的代码尽可能通用,我认为基于函数std::getstd::tuple_sizestd::tuple_element编写算法是个好主意,这样你只需要专门化这些函数就可以使用我的算法。我们将需要这些函数专门化的概念称为Tuple
现在我正在尝试对Tuple的各个组件求和。函数声明应该像这样:
template <class Tuple>
int sum_components(const Tuple& t);

我猜这里涉及到很多模板编程,但是我就是不知道怎么做。

对于加法,我会使用全局+运算符的重载。

我正在使用c++1z。

2个回答

12

中,这非常容易。

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 语义)。(它在处理 rvalue invokers 时不是完美的,但那是非常特殊的情况)。

中,我建议您升级编译器。在中,我建议您升职加薪。


以上所有的操作都是对二叉树进行左或右折叠。在某些情况下,二叉树折叠可能更好,但这更加棘手。
如果您的“+”使用表达式模板,则由于生命周期问题,上述代码可能无法正常工作。在某些情况下,您可能需要添加另一种模板类型来使临时表达式树在之后转换为“转换为”,以便进行评估。

只需要一些 decltype(auto)的运用。 - T.C.
@T.C. 根据需要添加。最初省略它们是因为我不想考虑表达式模板和其他混乱的问题。有一种可能性,从临时自动存储对象的字段返回的T&&中间结果可能会与上述内容发生冲突。但现在我认为这是表达式模板的问题。 - Yakk - Adam Nevraumont
非常感谢。这个解决方案比@krzaq的更好,因为您使用了std::apply,而我以前不知道。我接受了您的解决方案。 - Max
在C++03中,我建议你升级你的工作。说得好:o) - Arnaud

7

使用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>{});
}

演示


一个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>{});
}

演示

使用C++11的方法是使用自定义实现的index_sequence的C++14方法。例如从这里


正如@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));
}

演示

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

演示


1
有趣的是你在C++14中使用了左折叠,而在C++17中使用了右折叠。;-] - ildjarn
同意,对于sum来说这不应该有影响(尽管+并不一定安全 - 对于std::string), 但是我认为,一般情况下,第一次接触折叠表达式的人们应该展示左折叠,因为这是大多数C++程序员所期望的(任何具有FP背景的人一旦知道两种方法都可以实现,就不会被欺骗)。这不是对你的答案的批评,只是一种观察。 :-D - ildjarn
@ildjarn 等等,C++14解决方案中真的是左折叠吗?在cppreference页面上,它被描述为右折叠,即e1 + ( e2 + ( e3 + (en-1 + en)))。 - krzaq
@krzaq:这个过程是反向处理值,因此肯定会失去可交换性(演示);再次强调,对于sum来说不应该是问题,但一般情况下可能会有问题...只要+=不是病态的,这个就可以解决问题,尽管它并不完美... - ildjarn
1
@krzaq:链接错误?假设你的意思是这个,它似乎可以解决问题。:-D 终止情况虽然已经明确地打了类型,但我不喜欢;修复需要在某处使用std::common_type以及额外的模板参数来传递它-糟糕。 <3 折叠表达式... - ildjarn
显示剩余12条评论

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