質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

2回答

2570閲覧

C++ - std::for_each_n() が導入された理由について

tiitoi

総合スコア21956

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

3グッド

2クリップ

投稿2021/01/17 07:19

C++17 で追加された「先頭から n 個の範囲に関数を適用する」という処理を行う std::for_each_n() は、これまであった std::for_each()std::for_each(begin, begin + n) と指定すれば同じことができると思うのですが、なぜ別の関数として追加されたのでしょうか。
これら2つはどのように使い分ければいいのでしょうか。

よろしくおねがいします。

cpp

1#include <algorithm> 2#include <iostream> 3#include <vector> 4 5int main() 6{ 7 std::vector<int> v = {1, 2, 3, 4, 5}; 8 9 // for_each 10 std::for_each(v.begin(), v.begin() + 3, [](int x) { std::cout << x << " "; }); 11 std::cout << std::endl; 12 13 // for_each_n 14 std::for_each_n(v.begin(), 3, [](int x) { std::cout << x << " "; }); 15 std::cout << std::endl; 16 17 // 上記2つの違いがわからない 18}
ttact, kaina, yohhoy👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

これら2つはどのように使い分ければいいのでしょうか。

通常のシングルスレッド逐次処理では、使い分けについて気にする必要はありません。その文脈において自然な方(イテレータペア指定 or 個数指定)を使えばよいでしょう。

第1引数に 実行ポリシーExecutionPolicy をとるマルチスレッド並列処理に非ランダムアクセスイテレータ(前方向イテレータや双方向イテレータ)を指定するケースでは、std::for_each関数 よりも std::for_each_n関数 の方が効率的に処理を実行できる可能性があります。

std::for_each走査対象範囲を複数スレッドにチャンク分割するとき、「要素総数の計算」が ランダムアクセスイテレータでは計算量 O(1) で済みますが、非ランダムアクセスイテレータでは計算量 O(N) を必要とします。std::for_each_nならばこの計算が必要ありません。


なぜ別の関数として追加されたのでしょうか。

std::for_each_nは、C++17標準ライブラリに追加された「並列アルゴリズム」の一部として追加されました。

並列アルゴリズムは Thrustライブラリ として標準化を目指して検討されてきた経緯があり、当時からthrust::for_eachthrust::for_each_nの2種類が存在しました。

2012年頃のC++標準化提案文書 N3408 Parallelizing the Standard Algorithms Library には下記言及があります。要素数を指定する他の並列アルゴリズム(thrust::generate_nなど)の、実装を補助する道具としての側面があったようです。

5.1 std::for_each versus thrust::for_each and thrust::for_each_n
std::for_each assumes the function object it receives contains state which will be mutated inside a serial loop. It returns a copy of this state as a result. std::for_each_n does not exist.

By contrast, thrust::for_each and thrust::for_each_n instantiate many copies of their function object parameter in parallel. In such a setting, shared mutable state within the function object is a performance hazard at best. At worst, it is impossible for some parallel substrates to achieve. Instead, thrust::for_each and thrust::for_each_n exist exclusively for the sake of the side effects they may induce on the elements of their input ranges.

For consistency with other higher-level algorithms, thrust::for_each and thrust::for_each_n return the end of their input range. This simplifies the implementation of algorithms such as thrust::generate_n which can be lowered onto thrust::for_each_n in a straightforward manner.

投稿2021/01/19 07:11

編集2021/01/19 08:28
yohhoy

総合スコア6191

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tiitoi

2021/01/19 12:09

std::for_each_n の使い所及び導入された経緯について、解説してくださりありがとうございます。ランダムアクセスでないイテレータのときは for_each_n のほうを使ってみますね。ExecutionPolicy もデフォルト以外試したことなかったので、使ってみたいと思います。
episteme

2021/01/19 12:23

あーなるほど、Thrust由来か。納得。
yohhoy

2021/01/22 08:00

回答中では for_each_n が有利になるケースを説明していますが、実際問題としては「非ランダムアクセスイテレータ(を提供するコンテナ)+要素数は既知」という条件を満たすケースは稀かと思います。 C++標準アルゴリズムはイテレータ・ペアに対する操作がキホンですから、通常は for_each だけ覚えておけば十分な気はします。 P.S. C++20からは従来イテレータ・ペアに加えて、範囲(Range)サポートが追加されます。
guest

0

ベストアンサー

イテレータは様々なコンテナを想定します。 ランダムアクセスが可能とは限りません。 (イテレータに足し算が可能であるとは限りません。)

配列や std::array std::vector std::string といったように要素が隣接することが保証されるデータ型では質問者が考えるような足し算で問題ありませんが、そうでない構造を持つ場合にはイテレータのインクリメントを一定回数繰り返す (std::advance) という形で進めるしかありません。 非効率ですので、上限を数値で指定できる方法があれば効率的に出来る可能性があります。

要素が隣接することが保証されないコンテナを途中まで辿るときには std::for_each_n が必要だということになります。

投稿2021/01/17 07:57

SaitoAtsushi

総合スコア5684

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tiitoi

2021/01/17 08:02

ランダムアクセスできないイテレータにも適用できるようにするという意図でしたか。 理解できました。迅速な回答ありがとうございます。
episteme

2021/01/17 08:08

for_each( c.eagin(), next(c.begin(), n) ... ) って書けるんだから、 for_each「だけ」が _n 付きのが追加された理由としては弱い気がする。
kazuma-s

2021/01/17 12:00 編集

> for_each( c.eagin(), next(c.begin(), n) ... ) って書けるんだから、 c が list の場合、next でリンクを n 回たどって終了位置のイテレータを取得します。 そして、for_each でもう一度 list の先頭からリンクを n回たどります。二度リンクをたどります。 for_each_n なら、list のリンクをたどるのは一度で済みます。
episteme

2021/01/17 14:29

フォローありがとです。 そうであったにせよ、for_each「だけ」が _n 付きのが追加された理由としては弱いんちゃうかしら、
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問