P3168R1 Give std::optional Range Support が採択されそうなので、紹介します。
概要
std::optional
には C++23 でモナディックな操作を提供する変更が入りましたが、またしても利便性を向上させるために range を満たすようなインターフェイスが追加されます。
追加されるメンバなど
この提案書での変更はそう大きくなく、追加されるものをざっと示すと以下のようになります。
namespace std {
template <class T>
class optional {
using iterator = /* implementation-defined */;
using const_iterator = /* implementation-defined */;
constexpr iterator begin() noexcept;
constexpr const_iterator begin() const noexcept;
constexpr iterator end() noexcept;
constexpr const_iterator end() const noexcept;
/* existing members... */
};
template <class T>
constexpr bool ranges::enable_view<optional<T>> = true;
template <class T>
constexpr auto format_kind<optional<T>> = range_format::disabled;
}
begin, end
constexpr iterator begin() noexcept;
constexpr const_iterator begin() const noexcept;
constexpr iterator end() noexcept;
constexpr const_iterator end() const noexcept;
std::optional
を要素数が 0 または 1 のコンテナであるかのように扱う、メモリ連続(contiguous)なイテレータを提供します。
具体的には、 opt.has_value()
が真のとき *opt.begin()
は *opt
と等しく、そうでなければ opt.begin() == opt.end()
です。
std::optional<int> opt;
for (auto&& ref : opt) ref *= 2;
// equivalent to
opt.transform([](auto&& ref) { ref *= 2; });
std::ranges::enable_view
の特殊化
template <class T>
constexpr bool ranges::enable_view<optional<T>> = true;
ある型を view として扱うためには std::ranges::view_interface<Derived>
を継承するか std::ranges::enable_view
を特殊化するかの二通り方法がありますが、前者は ABI 互換性を保つのに難があるため、後者の方法が取られます。
static_assert( std::ranges::view<std::optional<int>> ); // C++23 で false, C++26 で true
std::format_kind
の特殊化
template <class T>
constexpr auto format_kind<optional<T>> = range_format::disabled;
こちらは一見マイナーな変更ではありますが、P2286R8 Formatting Ranges や P2585R1 Improve default container formatting などで導入された任意の range を std::format
でフォーマットできる機構からオプトアウトするための特殊化です。
これによって std::formatter<std::optional<T>>
を明示的に特殊化しない限り std::optional
が std::format
によってフォーマットされなくなります。
既存のコードに対する影響について
例えば以下のような C++20 のコードについてです。
#include <concepts>
#include <optional>
#include <ranges>
template <class T>
concept optional_like = requires(T x) {
{ x.has_value() } -> std::convertible_to<bool>;
{ *x } -> std::convertible_to<typename std::remove_cvref_t<T>::value_type const&>;
};
constexpr auto func(auto) { return 0; } // #0
constexpr auto func(std::ranges::range auto) { return 1; } // #1
constexpr auto func(optional_like auto) { return 2; } // #2
static_assert(func(std::optional<int>(42)) == 2);
#0
~ #2
の3つのオーバーロードを持つ関数テンプレートに対しこの提案が適用される前は std::ranges::range
と optional-like
を同時に満たす型を想定していないために static_assert
が通りますが、適用後はちょうど std::optional
がその条件を満たしてしまい #1
と #2
とでオーバーロード解決が曖昧となりコンパイルエラーになります。
こういったトリッキーな型に対する対策が十分に取られていないコードには影響が出ますが、ある程度テスト体制が整っているならば避けやすい影響であると思われます。
備考
元々は P1255R12 A view of 0 or 1 elements: views::maybe といった提案において std::views::maybe
という「要素が 0 または 1 個であるような range」が導入されようとしていましたが、そのコンセプトは std::optional
と酷似しており、また Rust の Option<T>
や Scala の Option[T]
などの型がイテレーション操作を提供していることを鑑みて std::optional<T>
に range インターフェイスを追加しようという運びになったようです。
また本提案書の著者を含めた標準化委員は P1255R12 で追加を提案されていた std::views::nullable
という nullable なオブジェクトを「要素が 0 または 1 個であるような range」化するアダプタ自体は支持しており、次のリビジョンでは std::views::maybe
が削除されています。
参照
P3168R1 Give std::optional Range Support
P1255R12 A view of 0 or 1 elements: views::maybe
P1255R13 A view of 0 or 1 elements: views::nullable