3

我正在玩元组和模板。我知道如果练习你会使用 boost::fusion (我认为)来做这种事情。我正在尝试在元组上实现等效的 std::accumulate 。

以下是我拥有的代码。据我所知,编译错误是由它尝试使用 4 模板参数版本引起的,而我打算将它用于 3 模板参数版本来完成递归。这意味着我错过了函数重载决议的一些内容。

我曾认为,由于两个函数都可以匹配,因此它会选择 3 个模板参数版本作为更好的匹配,因为最后一个参数类型已明确说明。如果我将 std::tuple_size 作为附加模板参数添加到 tuple_accumulate_helper 的两个版本,我仍然会得到相同的行为。

谁能建议我做错了什么?

#include <tuple>

template <std::size_t I>
struct int_{};

template <typename T, typename OutT, typename OpT, std::size_t IndexI>
auto tuple_accumulate_helper(T tuple, OutT init, OpT op, int_<IndexI>) -> decltype(tuple_accumulate_helper(tuple, op(init, std::get<IndexI>(tuple)), op, int_<IndexI + 1>()))
{
   return tuple_accumulate_helper(tuple, op(init, std::get<IndexI>(tuple)), op, int_<IndexI + 1>());
}

template <typename T, typename OutT, typename OpT>
auto tuple_accumulate_helper(T tuple, OutT init, OpT op, int_<std::tuple_size<T>::value>) -> decltype(init)
{
   return init;
}

template <typename T, typename OutT, typename OpT>
auto tuple_accumulate(T tuple, OutT init, OpT op) -> decltype(tuple_accumulate_helper(tuple, init, op, int_<0>()))
{
   return tuple_accumulate_helper(tuple, init, op, int_<0>());
}

struct functor
{
   template <typename T1, typename T2>
   auto operator()(T1 t1, T2 t2) -> decltype(t1 + t2)
   {
      return t1 + t2;
   }
};

int main(int argc, const char* argv[])
{
   auto val = tuple_accumulate(std::make_tuple(5, 3.2, 7, 6.4f), 0, functor());
   return 0;
}
4

3 回答 3

4

我不知道你是否感兴趣,但如果你可以使用一点提升,你可以有这个“开箱即用”:

#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/fusion/include/algorithm.hpp>
#include <boost/phoenix/phoenix.hpp>

using namespace boost::phoenix::arg_names;

#include <iostream>

int main()
{
    auto t = std::make_tuple(5, 3.2, 7, 6.4f); 

    std::cout << boost::fusion::accumulate(t, 0, arg1 + arg2) << std::endl;
    std::cout << boost::fusion::accumulate(t, 1, arg1 * arg2) << std::endl;
}

印刷

21.6
716.8
于 2013-09-01T23:14:56.513 回答
2

你不能让编译器匹配依赖于嵌套类型的模板特化,std::tuple_size<T>::value在你的例子中,[部分特化类型嵌套在模板类中]所以你必须找到替代方法让编译器知道递归何时结束.

在下面的代码片段中,我提供了一个替代方案。我绝不声称我的解决方案无论如何都是“最好的”,但我相信它可能有助于向您展示如何解决这个问题:

#include <cstddef>
#include <tuple>
#include <utility>
#include <type_traits>
#include <iostream>


//deduces the type resulted from the folding of a sequence from left to right
//avoids the decltype nonsense
template <typename T, typename OpT>
class result_of_acumulate;

template <typename... ArgsT, typename OpT>
class result_of_acumulate<std::tuple<ArgsT...>, OpT>
{
private:
    template <typename... ArgsHeadT>
    struct result_of_acumulate_helper;

    template <typename ArgsHeadT>
    struct result_of_acumulate_helper<ArgsHeadT>
    {
        typedef ArgsHeadT type;
    };

    template <typename ArgsHead1T, typename ArgsHead2T>
    struct result_of_acumulate_helper<ArgsHead1T, ArgsHead2T>
    {
        typedef typename std::result_of<OpT(ArgsHead1T, ArgsHead2T)>::type type;
    };

    template <typename ArgsHead1T, typename ArgsHead2T, typename... ArgsTailT>
    struct result_of_acumulate_helper<ArgsHead1T, ArgsHead2T, ArgsTailT...>
    {
        typedef typename result_of_acumulate_helper<typename std::result_of<OpT(ArgsHead1T, ArgsHead2T)>::type, ArgsTailT...>::type type;
    };

public:
    typedef typename result_of_acumulate_helper<ArgsT...>::type type;
};

template <std::size_t IndexI, typename T, typename OutT, typename OpT>
constexpr typename std::enable_if<(IndexI == std::tuple_size<T>::value), OutT>::type
tuple_accumulate_helper(T const& /*tuple*/, OutT const& init, OpT /*op*/)
{
    return init;
}

template <std::size_t IndexI, typename T, typename OutT, typename OpT>
constexpr typename std::enable_if
<
    (IndexI < std::tuple_size<T>::value),
    typename result_of_acumulate<T, OpT>::type
>::type
tuple_accumulate_helper(T const& tuple, OutT const init, OpT op)
{
    return tuple_accumulate_helper<IndexI + 1>(tuple, op(init, std::get<IndexI>(tuple)), op);
}

template <typename T, typename OutT, typename OpT>
auto tuple_accumulate(T const& tuple, OutT const& init, OpT op)
    -> decltype(tuple_accumulate_helper<0>(tuple, init, op))
{
    return tuple_accumulate_helper<0>(tuple, init, op);
}

struct functor
{
    template <typename T1, typename T2>
    auto operator()(T1 t1, T2 t2)
        -> decltype(t1 + t2)
    {
        return t1 + t2;
    }
};

int main(int /*argc*/, const char* /*argv*/[])
{
    auto val = tuple_accumulate(std::make_tuple(5, 3.2, 7U, 6.4f), 0L, functor());
    std::cout << val << std::endl; //should output 21.6
    return 0;
}

在 Archlinux x64 机器上使用 gcc (GCC) 4.8.1 20130725 (prerelease) 成功编译和测试。

于 2013-09-01T20:28:35.097 回答
0

一个建议:因为 std::tuples 可以携带非数字类型,你应该让你的仿函数意识到这种可能性并在编译时警告你。

这是使用 SFINAE 的建议

struct functor
{
   template <typename T1, typename T2,
   typename std::enable_if< std::is_arithmetic<T1>::value == true && 
   std::is_arithmetic<T2>::value == true , bool>::type = false >
   auto operator()(T1 t1, T2 t2) -> decltype(t1 + t2)
   {
      return t1 + t2;
   }
};

类型特征对于限制要在模板函数/类/函子中接受的类型非常重要

更新 1:另一个使用 static_assert 的建议(触发时会给出明确的错误消息)

struct functor
{
   template <typename T1, typename T2 >
   auto operator()(T1 t1, T2 t2) -> decltype(t1 + t2)
   {
      static_assert( std::is_arithmetic<T1>::value == true && 
   std::is_arithmetic<T2>::value == true )
      return t1 + t2;
   }
};
于 2013-09-01T22:24:13.843 回答