最終更新日時(UTC):
が更新

履歴 編集

concept
<iterator>

std::indirectly_writable(C++20)

namespace std {
  template<class Out, class T>
  concept indirectly_writable = 
    requires(Out&& o, T&& t) {
      *o = std::forward<T>(t);
      *std::forward<Out>(o) = std::forward<T>(t);
      const_cast<const iter_reference_t<Out>&&>(*o) = std::forward<T>(t);
      const_cast<const iter_reference_t<Out>&&>(*std::forward<Out>(o)) = std::forward<T>(t);
    };
}

概要

indirectly_writableは、任意の型Outが間接参照演算子(operator*)によって型Tの値を書き込み(出力)可能であることを表すコンセプトである。

イテレータ型に限らず、ポインタ型、スマートポインタ型などがこのコンセプトのモデルとなることができる。

要件

このコンセプトを構成する4つの制約式は全て、等しさを保持することを要求しない。

制約式について

const_castを用いる制約式は、非プロキシオブジェクト(値を所有するオブジェクトのことである。std::stringなどが該当)を弾くために存在する。

まず前提として、非プロキシオブジェクトはメンバ関数の左辺値/右辺値修飾が追加される以前からの慣習により、prvalueであっても代入が可能である。また、値を所有するためにconstが付与されれば代入は不可能となる。

#include <string>

int main()
{ 
  std::string()  = "string"; // prvalueに代入
}

このような非プロキシオブジェクトのprvalueがイテレータの間接参照結果として返された場合、それに書き込んだとしても、そのprvalueのオブジェクトが所有する値を書き換えるのみであり、正しく出力されるとは言えない。

一方で、プロキシオブジェクト(所有権を持たず、他のオブジェクトを参照するオブジェクト)がイテレータの間接参照結果として返された場合は、prvalueの場合も代入された値を「参照する先のオブジェクトが所有する値」へ書き込むことによって、正しく出力を完了することが出来る。

更に、プロキシオブジェクトは値を所有しないため「constな参照先」でなければ、プロキシオブジェクトそれ自体がconstであっても代入は可能であるべきである。しかしながら、従来のプロキシオブジェクトはconstを付与された場合代入が不可能になる実装が殆どであり、変更が必要である。例としてはstd::vector<bool>::referenceがある。

プロキシオブジェクトの他にも、参照(右辺値参照/左辺値参照問わず)も正しく出力が可能である。このことから、「constを付与した上でも代入が可能であるようなプロキシオブジェクト」と「参照」を合わせて「プロキシ参照(proxy reference)」と呼称することがある。

以上のことから、constを付与した際にも代入が可能であればプロキシ参照、不可能であれば非プロキシ参照と区別することが可能となる。したがって、間接参照が非プロキシ参照(プロキシオブジェクトでないprvalueなど)を返すイテレータ型は本コンセプトのモデルとならず、プロキシ参照を返すイテレータは本コンセプトのモデルとなる事ができる。

モデル

decltype((E)) == Tとなるような式E及びdereferenceableな型Outのオブジェクトoについて次の条件を満たす場合に限って、型Out, Tindirectly_writableのモデルである。

  • Out, Tが次の制約式のモデルとなる場合、式Eを上記(定義内)式のいずれかの形で出力した後で、*oは元のEの値と等値(equal)になる。
    • ただし、そのような出力の後でoが間接参照可能である必要はない。

Eの値カテゴリがxvalueの場合、上記式による出力後Eのオブジェクトは有効だが未規定な状態となる

備考

indirectly_writableのモデルとなるOutoperator*は出力(上記式による代入文の左辺に来ること)だけが唯一有効な使用法であり、indirectly_readableである必要はない。

C++20

#include <iostream>
#include <concepts>
#include <iterator>
#include <memory>
#include <vector>
#include <optional>

template<typename Out, typename T>
requires std::indirectly_writable<Out, T>
void f(const char* out, const char* t) {
  std::cout << out << " is indirectly writable " << t << std::endl;
}

template<typename Out, typename T>
void f(const char* out, const char* t) {
  std::cout << out << " is not indirectly writable " << t << std::endl;
}


struct writable {
  int& operator*();
};

struct not_writable {
  //prvalueを返す、出力不可能
  int operator*();
};


int main() {
  f<int*, int>("int*", "int");
  f<std::unique_ptr<int>, int&>("std::unique_ptr<int>", "int&");
  f<std::vector<int>::iterator, int&&>("std::vector<int>::iterator", "int&&");
  f<std::ostream_iterator<double>, double>("std::ostream_iterator<double>", "double");
  f<writable, int>("writable", "int");

  std::cout << "\n";
  f<std::vector<bool>::iterator, bool>("std::vector<bool>::iterator", "bool");
  f<std::optional<int>, int>("std::optional<int>", "int");
  f<not_writable, int>("not_writable", "int");
}

出力

int* is indirectly writable int
std::unique_ptr<int> is indirectly writable int&
std::vector<int>::iterator is indirectly writable int&&
std::ostream_iterator<double> is indirectly writable double
writable is indirectly writable int

std::vector<bool>::iterator is not indirectly writable bool
std::optional<int> is not indirectly writable int
not_writable is not indirectly writable int

C++23 で満たすようになった型

#include <iostream>
#include <concepts>
#include <iterator>
#include <utility>
#include <tuple>
#include <vector>

template <typename Out, typename T>
requires std::indirectly_writable<Out, T>
void f(const char* out, const char* t)
{
    std::cout << out << " is indirectly writable " << t << std::endl;
}

template <typename Out, typename T>
void f(const char* out, const char* t)
{
    std::cout << out << " is not indirectly writable " << t << std::endl;
}

struct ret_proxy_pair
{
  // 全ての要素がプロキシ参照である pair はプロキシ参照
    std::pair<int&, std::pair<int&, int&>> operator*();
};

struct ret_proxy_tuple
{
  // 全ての要素がプロキシ参照である tuple はプロキシ参照
    std::tuple<int&, std::tuple<int&>> operator*();
};


int main()
{
    f<std::vector<bool>::iterator, bool>("std::vector<bool>::iterator", "bool");
    f<ret_proxy_pair, std::pair<int, std::pair<int, int>>>("ret_proxy_pair", "std::pair<int, std::pair<int, int>>");
    f<ret_proxy_tuple, std::tuple<int, std::tuple<int>>>("ret_proxy_tuple", "std::tuple<int, std::tuple<int>>");
}

出力

std::vector<bool>::iterator is indirectly writable bool
ret_proxy_pair is indirectly writable std::pair<int, std::pair<int, int>>
ret_proxy_tuple is indirectly writable std::tuple<int, std::tuple<int>>

バージョン

言語

  • C++20

処理系

関連項目

参照