filter_viewがconst-iterableではない理由
本当に全てのviewがconst-iterable?
→NO
range adopterだけ見ても、drop_while_view
もconst-iterableではない。
filter_view
- 指定された条件を満たさない要素を落とす
-
begin()
の呼び出しは、まず一番最初の条件を満たす要素までイテレータを進めて、それを返す - その際、求めたイテレータをキャッシュする
drop_while_view
- 指定された条件を満たす要素を先頭から取り除く
-
begin()
の呼び出しは、まず一番最初の条件を満たさない要素までイテレータを進めて、それを返す - その際、求めたイテレータをキャッシュする
共通点は、begin()
ではベースのrange
のイテレータを場合によってはある程度進めてしまう事、そしてその結果をキャッシュすること。
キャッシュが問題なのか?
drop_view
もbegin()
の呼び出しで指定された数イテレータを進めてそれを返し、求めたイテレータはキャッシュする。
キャッシュ動作の有無はconst-iterableとは関係ない?
標準ライブラリのクラスのメンバ関数に対するconst
修飾は、スレッドセーフであることを表明する。
view
がキャッシュ機構を持ちbegin()
がそれを利用する場合、begin()
はconst
ではいられず、const
メンバ関数であるためには追加の同期を必要とする。多くの場合それはユーザーの責任によって行われる。
そのため、キャッシュ機構を持つview
は本来const-iterableとはならない。
では逆に、なぜdrop_view
はconst-iterableなのか?
namespace std::ranges {
template<view V>
class drop_view : public view_interface<drop_view<V>> {
public:
constexpr auto begin()
requires (!(simple-view<V> && random_access_range<V>));
constexpr auto begin() const
requires random_access_range<const V>;
};
}
drop_view
のbegin()
メンバ関数のconst
オーバーロードは制約されており、ベースのrange
(をconst
修飾したもの)がrandom_access_range
である場合にのみ利用可能となる。
すなわち、ベースのrange
のイテレータがrandom access iteratorであるときにのみ、drop_view
はconst-iterableである。
なぜかというと、ランダムアクセスイテレータの進行は範囲の長さや進む距離によらず一定(O(1))で行うことができる。すなわち、キャッシュ機構を用いなくてもrange
コンセプトの意味論要件(呼び出しは償却定数時間であること)を満たすことができる。
従って、drop_view
はベースのrange
がrandom_access_range
である場合、そのbegin()
の呼び出しはキャッシュ機構を使用せず、それによってconst
オーバーロードが提供可能であり、あとはベースのrange
がconst-iterableならば、drop_view
もconst-iterableとる。
filter_view
とdrop_while_view
はいずれも、落とす(残す)要素の数を指定するのではなく、条件をしていする。
満たすものを残すのか落とすのかの差異はあれど、begin()
の呼び出しで返すイテレータは指定された条件で決まる。すなわち、一番先頭かもしれないし終端になるかもしれない。
従って、ベースのrange
が何であっても、条件に合う要素を見つけるまで必ず要素を一つづつ見て行く必要がある。そこでは、ベースのrange
の長さをNとすると最大でO(N)の時間がかかる。
drop_view
の時とは異なり、random_access_range
であったとしても見るべき要素の数は変わらず、キャッシュ機構の利用が避けられない。すなわち、begin()
のconst
オーバーロードを提供できない。
filter_view
(とdrop_while_view
)がconst-iterableではない(begin()/end()
のconst
オーバーロードがない)のは、どんな場合でもキャッシュの利用が避けられず、追加の同期なしではスレッドセーフ性を保証できないため。だと思われる。
FYI: P0896R4 The One Ranges Proposal(Adopted 2018-11) から P0789R3 Range Adaptors and Utilities と辿ったところ、Design Considerations > The filter
adaptor is not const
-iterableに背景説明がありました。
- Compute the first position once in begin on demand and cache the result, without synchronization. The downside of this approach is that begin cannot be const without violating [res.on.data.races].
None of these are great options, and this particular design point has been discussed at extensive length (see range-v3#385) in the context of the
filter
view and an assortment of other adaptors that are unable to provideconst
iteration. The general consensus is that option (4) above is the least bad option, and is consistent with the perspective that adaptors are lazily-evaluated algorithms: some algorithms can be implemented without the need to maintain mutable state. Others cannot.
drop_while_view
は別提案 P1035R7 Input Range Adaptors(Adopted 2019-07) 経由でしたが、filter
同様にonihusbeさん考察通りだと思われます。