まだプレビュー版なので開発には使えませんが、Visual Studioのプレビュー版(Microsoft Visual Studio Community 2019 Preview Version 16.8.0 Preview 3.0)でC++20のRanges主要機能がサポートされました。
コンセプトと合わせてとてもおもしろい機能だったので、興奮が冷めないうちにRangesとRange adaptorsについて学んだことを記録します。経験に基づく自己流の解釈なので間違っていたら教えてください。
コードの実行時はプロジェクトのプロパティで「C++言語標準」を/std:c++latestに設定します。
Visual Studio 2019 version 16.8 Preview 3 - Visual Studio 2019 Preview Release Notes
Rangesとコンセプト
Rangesは次のrangeのような「コンセプト」を満たす型やそれらに与えられる機能の総称?です。rangeコンセプトの他にもcommon_rangeやviewable_rangeが存在します。
template<class T>
concept range = requires(T& t) {
ranges::begin(t);
ranges::end(t);
};
参考:https://en.cppreference.com/w/cpp/ranges/range。見やすさのために一部改変。
コンセプトはC++20から導入されたので聞き慣れない単語ですが、これまでメタ関数で複雑に記述していた型の制限をより分かりやすく記述するための方法です。例えば上記のrange(std::ranges::range)はranges::begin(t)とranges::end(t)が適用できるあらゆる型です。
rangeにはSTLコンテナではstd::vector、std::list等が該当しており、beginとendを実装する自作のクラスや構造体も該当します。例えばstd::views::transformはrangeから派生したコンセプトviewable_rangeに従う型に適用できるので以下のコードが書けます。
# include <iostream>
# include <ranges>
# include <vector>
class test {
public:
test() : v{ 0, 1, 2, 3 } {}
auto begin() { return v.begin(); }
auto end() { return v.end(); }
private:
std::vector<int> v;
};
int main()
{
test t;
auto mul2 = [](int x) { return x * 2; };
for (auto x : t | std::views::transform(mul2))
std::cout << x << std::endl;
// 0, 2, 4, 6
return 0;
}
Range adaptors
上のサンプルコードに| std::views::transform(...)という記述が登場しています。このときの呼び出しを順番に説明すると以下の通りです。
-
std::views::transformはstd::views名前空間で定義された_Transform_fn型の変数。 -
_Transform_fn型は_Copy_constructible_objectコンセプトを満たす型_Fnに対するoperator(_Fn _Fun)を定義しており、transform(...)はこれを呼び出す。 - 上記の
operator(_Fn _Fun)は_Transform_fn型でprivate定義された_Partial<_Fn>型のオブジェクトを返す。 -
_Partial<_Fn>型は_Pipe::_Base<_Partial<_Fn>>型から派生しており、この_Pipe::_Base<_Partial<_Fn>>型が定義する右辺用の|演算子が呼び出される(| std::views::transform(...)の|)。 -
_Partial<_Fn>型はtransform_viewを返す()演算子を定義しており、上記の|演算子がこれを呼び出す。
<ranges>の中を行ったり来たりしますが、コンセプトという新機能を使った見事な仕組みです。