2017-11-10 17 views
3

たとえばstd::tuplefold関数を書きたいと思っていました。指定されたタプル内のすべての要素の合計(または積)。例えば、C++タプルのfold/sum関数の記述方法は?

std::tuple<int,double> t = std::make_tuple(1,2); 

与えられた私は

auto s = sumT(t); //giving 3 

私が試したが、コンパイルするには、以下の私のテンプレートプログラミング(C++ 11/1Z)コードを取得できませんでしたを計算したいと思います。私はまた、私の他の質問(How to perform tuple arithmetic in C++ (c++11/c++17)?)のために受け入れられた答えを適応させようとしましたが、この場合にstd::index_sequenceの使い方を理解することはできません。

私がしているしている問題:

1)私は、例えば、種類を把握することはできません戻り値の型として最初の要素の型を使用する方法。現在、私は_res型をテンプレートに使用していますが、C++の自動型推論をブロックするかどうかはわかりません。

2)明示的な初期要素0を使用せずにこれをプログラムしたいので、これは他の種類のfold操作に使用できます。

現在、再帰は最後の要素で終了します。私は_size - 1で再帰を終了したいので、0に頼らずに最後の要素の操作を直接実行することができます。

以下のコードでは、再帰によってこれを実行しようとしています。しかし、私はテンプレートプログラミングをよく知っていませんし、ループがタプルのためにどのように機能するかはわかりません。

誰かがコードを修正したり、より良い解決策を考えたりできますか?

私のコードは、これまでのところです:

#include <tuple> 
#include <iostream> 
#include <functional> 

// helper class for fold operations 
template<typename Op,typename _res, typename _Tp, size_t _i, size_t _size> 
struct _tuple_fold { 
    static constexpr _res _op(Op const & op, const _Tp& _t) { 
     return _res(op(std::get<_i>(_t), 
       _tuple_fold<Op, _res, _Tp, _i + 1, _size>::_op(op,_t))); 
    } 
}; 

template<typename Op,typename _res,typename _Tp, size_t _size> 
struct _tuple_fold<Op, _res,_Tp, _size, _size> { 
    static constexpr _res _op(Op const &, const _Tp&) { return 0; } 
}; 

template <typename ... Ts> 
auto sumT (std::tuple<Ts...> const & t1) { 
    return _tuple_fold::_op(std::plus<>{}, t1); 
} 

int main() { 
    std::tuple<int,double> t = std::make_tuple(1,2); 
    auto s = sumT(t); 
    std::cout << s << std::endl; 
} 

g++ -std=c++17 tuple_sum.cppでコンパイルするためのエラーメッセージ:

tuple_sum.cpp: In function ‘auto sumT(const std::tuple<_Elements ...>&)’: 
tuple_sum.cpp:21:10: error: ‘template<class Op, class _res, class _Tp, long unsigned int _i, long unsigned int _size> struct _tuple_fold’ used without template parameters 
    return _tuple_fold::_op(std::plus<>{}, t1); 
     ^
tuple_sum.cpp: In function ‘int main()’: 
tuple_sum.cpp:27:19: error: ‘void s’ has incomplete type 
    auto s = sumT(t); 
       ^

私は呼び出しサイト上_tuple_foldの型パラメータを指定するかどうかはわかりません特にstd::plusのタイプです。

+1

[STD ::累算](http://en.cppreference.com/w/cpp/algorithm/accumulate)はラムダでうまくいくと思います。 –

+0

@JesperJuhlいいえ、etherogeneousコンテナ(タプルのような)から普通のイテレータを取ることはできません –

答えて

3

はありがたいことに、C++ 17でif constexprは、私たちは、部分的に専門のヘルパー構造体を導入することで、物事を複雑に避けることができます、と私たちは好きな条件で再帰を終了させることが容易になります:

#include <functional> 
#include <iostream> 
#include <tuple> 
#include <type_traits> 

template <size_t index, class Op, class... Ts> 
constexpr auto tuple_fold(Op op, const std::tuple<Ts...>& t) { 
    if constexpr(index == sizeof...(Ts) - 1) { 
     return std::get<index>(t); 
    } else { 
     return op(std::get<index>(t), tuple_fold<1 + index>(op, t)); 
    } 
} 

template <typename ... Ts> 
constexpr auto sumT (std::tuple<Ts...> const & t1) { 
    return tuple_fold<0>(std::plus<>{}, t1); 
} 

int main() { 
    std::tuple<int,double> t = {1, 2.0}; 
    auto s = sumT(t); 
    static_assert(std::is_same_v<decltype(s), double>); 
    std::cout << s << std::endl; 
} 

Coliruリンク:http://coliru.stacked-crooked.com/a/1e7051b8652fb942

これは右折(a + (b + (c + ...)))を実行しますが、必要に応じて左折を実行するために書き直すのは簡単です。

12

C++ 17で我々は()適用できることに注意してください:

auto t = std::make_tuple(1, 2.); 
auto sum = std::apply([](auto... v){ return (v + ...); }, t); 

を、これは任意のタプルのようなタイプのために働くと「+」のための通常のプロモーション/変換規則に従うだろう(これは望ましくないかもしれません)。上記の私は算術型を扱っているので値渡しですが、もちろんあなたの優先転送戦略を適用することもできます...

+0

これは素晴らしいことです。 –

0

の左右の組み込み関数を利用して、バイナリ演算。

template<class F, class Lhs=void> 
struct invoke_by_times_t { 
    F& f; 
    Lhs lhs; 
    template<class Rhs> 
    auto operator*(Rhs&& rhs)&& 
    ->invoke_by_times_t<F, std::invoke_result_t< F&, Lhs, Rhs >> 
    { 
    return { 
     f, 
     f(std::forward<Lhs>(lhs), std::forward<Rhs>(rhs)) 
    }; 
    } 
}; 

template<class F> 
struct invoke_by_times_t<F, void> { 
    F& f; 
    template<class Rhs> 
    invoke_by_times_t<F, Rhs> operator*(Rhs&& rhs)&&{ 
    return {f, std::forward<Rhs>(rhs)}; 
    } 
}; 

template<class F> 
auto fold_over(F&& f) { 
    return [f=std::forward<F>(f)](auto&&...args)mutable{ 
    return (invoke_by_times_t<F>{f}*...*decltype(args)(args)).lhs; 
    }; 
} 

ここで任意のバイナリ関数を指定すると、再帰を行わずにそれをフォールドする関数オブジェクトを作成できます。

std::applyと一緒にされています。

template <typename ... Ts> 
auto sumT (std::tuple<Ts...> const & t1) { 
    return std::apply(fold_over(std::plus<>{}), t1); 
} 

Live example

これは左倍です。右折は単にfold_overの機能を変更するだけです。空のパックをこれに渡そうとすると、コンパイルに失敗します。 1つの要素を渡すと、その要素が返されます。

0

左倍:

// A helper struct 
template<class Op, class T, class... Ts> 
struct fold_left_helper { 
    const Op& op; 
    constexpr fold_left_helper(const Op& op) : op(op) { } 
    constexpr auto operator()(const Ts&... xs, const T& x) { 
     if constexpr(sizeof...(xs) == 0) return x; 
     else return op(fold_left_helper<Op, Ts...>(op)(xs...), x); 
    } 
}; 

// Left fold 
template<class Op, class... Ts> 
static constexpr auto fold_left(const Op& op, const std::tuple<Ts...>& t) { 
    return std::apply(fold_left_helper<Op, Ts...>(op), t); 
} 

右倍:

// A helper struct 
template<class Op, class T, class... Ts> 
struct fold_right_helper { 
    const Op& op; 
    constexpr fold_right_helper(const Op& op) : op(op) { } 
    constexpr auto operator()(const T& x, const Ts&... xs) { 
     if constexpr(sizeof...(xs) == 0) return x; 
     else return op(x, fold_right_helper<Op, Ts...>(op)(xs...)); 
    } 
}; 

// Right fold 
template<class Op, class... Ts> 
static constexpr auto fold_right(const Op& op, const std::tuple<Ts...>& t) { 
    return std::apply(fold_right_helper<Op, Ts...>(op), t); 
} 

試験:

constexpr auto divide = [](auto x, auto y) { return x/y; }; 
constexpr auto tuple = std::make_tuple(8, 4, 2); 
static_assert(1 == fold_left(divide, tuple)); 
static_assert(4 == fold_right(divide, tuple)); 
関連する問題