Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
255 changes: 241 additions & 14 deletions docs/standardization/cpp20.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ int main()
15, 3, false
```

### メンバポインタ演算子の仕様を一貫性のために修正 [(P0704R1)](http://wg21.link/p0704r1)

### メンバポインタ演算子の仕様を一貫性のために修正 [(P0704R1)](http://wg21.link/p0704r1)
C++17 までのメンバポインタ演算子 `.*` は「右辺値オブジェクトから、左辺値参照修飾されたメンバ関数ポインタに使うことは不適格」という規格文面になっていました。そのため、同じ意味をもつ次の 2 つのプログラムで後者だけ不適格とされ、一貫性がありませんでした。
```C++
#include <iostream>
Expand Down Expand Up @@ -136,8 +136,8 @@ int main()
}
```

### 型名であることが明らかな文脈で `typename` を省略可能に [(P0634R3)](https://wg21.link/P0634R3)

### 型名であることが明らかな文脈で `typename` を省略可能に [(P0634R3)](https://wg21.link/P0634R3)
C++17 で依存名が型である場合に `typename` を付けないのは、派生クラス定義時の基底クラスの指定と、初期化子リストでの基底クラスの指定のみでした(厳密にはこの 2 つには `typename` を付けられません)。C++20 では、型名しか使えないさらにいくつかの文脈で `typename` が省略可能になります。次のサンプルコードの左右タブで比較できます。

```C++ tab="C++17"
Expand Down Expand Up @@ -166,7 +166,7 @@ template <class T> typename T::size_type MaxSize();

int main()
{
S<std::vector<std::string>> s;
S<std::vector<std::string>> s;
}
```

Expand Down Expand Up @@ -202,6 +202,195 @@ int main()
```


### 定数式での仮想関数呼び出しが可能に [(P1064R0)](https://wg21.link/P1064)
コンパイル時に決定可能であれば、参照やポインタを通した仮想関数の呼び出しを `constexpr` にできるようになります。`constexpr` 修飾された仮想関数を非 `constexpr` 関数でオーバーライドすることや、その逆も可能です。

```C++
struct Cpp
{
virtual int version() const = 0;
};

struct Cpp17 : Cpp
{
constexpr int version() const override
{
return 17;
}
};

struct Cpp20 : Cpp
{
constexpr int version() const override
{
return 20;
}
};

constexpr int GetVersion(const Cpp& a)
{
return a.version();
}

int main()
{
constexpr Cpp17 cpp17;
constexpr Cpp20 cpp20;

static_assert(GetVersion(cpp17) == 17);
static_assert(GetVersion(cpp20) == 20);
}
```


### `type_id` と `dynamic_cast` が constexpr に [(P1327R1)](https://wg21.link/P1327)
`dynamic_cast` と `type_id` が、例外を投げるケースを除いて `constexpr` になります。

```C++
#include <typeinfo>

struct Cpp
{
virtual int version() const = 0;
};

struct Cpp17 : Cpp
{
constexpr int version() const override
{
return 17;
}
};

struct Cpp20 : Cpp
{
constexpr int version() const override
{
return 20;
}
};

int main()
{
constexpr static Cpp17 cpp17;
constexpr const Cpp* pCpp = &cpp17;
constexpr auto& cpptype = typeid(*pCpp);

constexpr const Cpp& refCpp = cpp17;
constexpr const Cpp17& redCpp2 = dynamic_cast<const Cpp17&>(refCpp);
}
```

次のように例外を投げるケースでは `constexpr` にできずコンパイルエラーになります。

```C++
#include <typeinfo>

struct Cpp
{
virtual int version() const = 0;
};

struct Cpp17 : Cpp
{
constexpr int version() const override
{
return 17;
}
};

struct Cpp20 : Cpp
{
constexpr int version() const override
{
return 20;
}
};

int main()
{
constexpr Cpp* pCpp = nullptr;
constexpr auto& cpptype = typeid(*pCpp); //コンパイルエラー: 例外 std::bad_typeid を投げるため constexpr 不可

constexpr static Cpp17 cpp17;
constexpr const Cpp& refCpp = cpp17;
constexpr const Cpp20& redCpp2 = dynamic_cast<const Cpp20&>(refCpp); // コンパイルエラー: 例外 std::bad_cast を投げるため constexpr 不可
}
```


### 定数式において共用体のアクティブメンバの切り替えが可能に [(P1330R0)](https://wg21.link/P1330)
共用体のアクティブメンバとは、最後に初期化または値を代入したメンバのことです。C++17 では共用体の初期化やアクティブメンバへのアクセスを定数式で行えましたが、アクティブメンバの切り替えはできませんでした。定数式でのアクティブメンバの切り替えが可能になると、共用体によって実装される `std::string` や `std::optional` などの標準ライブラリクラスのメンバ関数の `constexpr` 対応を拡充できます。非アクティブメンバへのアクセスは未定義動作なので、定数式で行うとコンパイルエラーになります。

```C++
#include <cstdint>

union Value
{
float f;
std::uint32_t i;
};

constexpr Value GetFloat(float x)
{
return Value{ x }; // value.f がアクティブメンバ
}

constexpr Value GetUint(std::uint32_t x)
{
Value value = GetFloat(0.0f); // value.f がアクティブメンバ
value.i = x; // value.i がアクティブメンバに
return value;
}

int main()
{
static_assert(GetUint(123).i == 123);
}
```


### 定数式の文脈では `try-catch` を無視するように [(P1002R1)](https://wg21.link/P1002)
これまで `constexpr` 関数の中には `try-catch` ブロックを書くことができませんでした。しかし、`std::vector` 等のコンテナを `constexpr` 対応するにあたっては、この制限が障壁となるため、C++20 では `constexpr` 関数の中の `try-catch` は、定数式として評価されるときには無視するよう仕様が改められます。定数式の評価中に例外を投げるようであればコンパイルエラーになります。`std::vector` などを `constexpr` 対応させるための措置であり、将来の C++ におけるコンパイル時例外処理の実現を否定するものではありません。

```cpp
#include <cstdint>
#include <iostream>
#include <exception>

constexpr std::uint32_t AddU8(std::uint32_t a, std::uint32_t b)
{
if ((a + b) >= 256)
{
throw std::exception{};
}

return a + b;
}

constexpr std::uint32_t DoubleU8(std::uint32_t n)
{
try
{
return AddU8(n, n);
}
catch (const std::exception& except)
{
return 0;
}
}

int main()
{
static_assert(DoubleU8(123) == 246); // OK: 例外を投げずに定数式として評価可能

//static_assert(DoubleU8(200) > 0); // コンパイルエラー: 定数式として評価される constexpr 関数内で例外を投げるため

std::cout << "result: " << DoubleU8(200) << '\n'; // OK: 実行時に評価される関数で例外が発生する
}
```


## 標準ライブラリ

### 文字列の先頭や末尾が、ある文字列と一致するか判定 [(P0457R2)](https://wg21.link/P0457R2)
Expand Down Expand Up @@ -232,7 +421,6 @@ false


### `operator>>(basic_istream&, charT*)` の第二引数を `charT(&)[N]` に変更して安全に [(P0487R1)](https://wg21.link/P0487R1)

C++17 までの `operator>>(basic_istream&, charT*)` は、関数にバッファのサイズが渡されないため、次のようなプログラムでバッファオーバーフローへの対策が必要でした。
```C++
#include <iostream>
Expand Down Expand Up @@ -285,6 +473,7 @@ int main()
}
```


### 戻り値の無視が不具合をもたらす関数に `[[nodiscard]]` を付与 [(P0600R1)](https://wg21.link/P0600R1)
C++17 で導入された `[[nodiscard]]` 属性を標準ライブラリで活用するようになります。C++20 では付与基準を「戻り値の無視がトラブルやメモリリークなどの重大なエラーを引き起こす C++ の関数」とし、`async()`, `launder()`, `allocate()`, `empty()`, `operator new()` が対象となっています。
```C++
Expand All @@ -304,7 +493,6 @@ MSVC の標準ライブラリでは Visual Studio 2017 15.6 以降、規格の


### `<array>` ヘッダのすべての関数が constexpr に [(P1023R0)](https://wg21.link/P1023R0), [(P1032R1)](https://wg21.link/P1032R1)

C++17 の `<array>` ヘッダでは、比較演算子、`swap()`, `fill()` 以外のすべての関数が constexpr でした。C++20 ではさらに、array の比較演算の実装に使われている `std::equal()` と `std::lexicographical_compare()` が [constexpr になった (P0202R3)](https://wg21.link/P0202R3) ことにともない、array の比較演算子を constexpr とし、また `swap()` と `fill()` についても constexpr にすることを決め、array ヘッダのすべての関数が constexpr で提供されます。


Expand All @@ -317,7 +505,6 @@ C++17 の `<array>` ヘッダでは、比較演算子、`swap()`, `fill()` 以


### ポインタのアライメントを最適化ヒントとしてコンパイラに伝える `assume_aligned()` 関数 [(P1007R3)](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1007r3.pdf)

データのアドレスが 16 バイトなどのサイズにアライメントされている場合、コンパイラが SIMD を使った最適なコードを生成できる可能性があります。あるポインタの指すデータがアライメントされていることをコンパイラに伝える方法として、GCC や Clang では `__builtin_assume_aligned()` や `__attribute__((assume_aligned(alignment)))`, ICC では `__assume_aligned()` などの独自拡張がありますが、標準化された方法はありませんでした。C++20 では、これらの差異を吸収する次のような関数テンプレートが提供されます。
```C++
template <size_t N, class T>
Expand All @@ -338,7 +525,6 @@ void Multiply(float* x, size_t size, float factor)


### スマートポインタの作成時に値をデフォルト初期化する make 関数を追加 [(P1020R1)](https://wg21.link/P1020R1)

実行時性能のために、`float` や `unsigned char` など組み込み型の配列の値をデフォルト初期化させたい(ゼロ初期化しない)ケースがあります。しかし、`make_unique` や `make_shared`, `allocate_shared` でスマートポインタを作成した場合には値初期化が実行されます。C++20 では、値初期化をせずにデフォルト初期化でスマートポインタを作成する関数 `make_unique_default_init`, `make_shared_default_init`, `allocate_shared_default_init` が追加されました。
```C++
#include <iostream>
Expand Down Expand Up @@ -387,7 +573,6 @@ int main()


### 非順序連想コンテナのルックアップ操作で、`key_type` と比較可能な型を変換せずに使えるように [(P0919R3)](http://wg21.link/P0919r3)

C++17 までの `unorderd_map` や `unordered_set` など非順序連想コンテナでは、`find()`, `count()`, `equal_range()` などルックアップを行うメンバ関数は引数に `key_type` をとり、例えば次のようなケースで `std::string` 型の一時オブジェクトが作成されて非効率でした。

```C++
Expand Down Expand Up @@ -432,7 +617,6 @@ int main()


### 非順序連想コンテナのルックアップ操作に、計算済みハッシュ値を渡せるように [(P0920R2)](https://wg21.link/P0920R2)

同じ種類の非順序連想コンテナを複数扱い、同じキーを用いたルックアップ操作をそれらのコンテナに対して行うようなケースでは、キーのハッシュ値の計算を毎回行うのは冗長です。C++20 ではメンバ関数 `find()`, `count()`, `contains()`, `equal_range()` に、計算済みのハッシュ値を渡せるオーバーロードが追加されます。
```C++
#include <iostream>
Expand All @@ -459,7 +643,6 @@ int main()


### 2 つの値の中点を計算する `std::midpoint()` 関数 [(P0811R3)](https://wg21.link/P0811R3)

2 つの値 `a`, `b` の中点を計算する際に、単純な `(a + b) / 2` という式ではオーバーフローを起こす可能性があります。C++20 で追加される `std::midpoint()` 関数では、整数に対して
```C++
constexpr Integer midpoint(Integer a, Integer b) noexcept
Expand Down Expand Up @@ -494,7 +677,6 @@ int main()


### 2 つの値の線形補間を計算する `std::lerp()` 関数 [(P0811R3)](https://wg21.link/P0811R3)

2 点 `a`, `b` の間をパラメータ `t` によって線形補間する関数が提供されます。計算結果 `r` は `a + t * (b - a)` によって求められますが、実装により `isfinite(a) && isfinite(b)` のとき

- `lerp(a, b, 0) == a && lerp(a, b, 1) == b`
Expand Down Expand Up @@ -522,7 +704,6 @@ int main()


### 実装固有の情報をまとめる `<version>` ヘッダを追加 [(P0754R2)](https://wg21.link/P0754R2)

`__cpp_lib_byte`, `__cpp_lib_void_t` のような標準ライブラリの機能テストマクロ、その他ライブラリのバージョンや実装固有の情報をまとめる目的の `<version>` ヘッダが追加されました。
例えば C++20 以前の MSVC の標準ライブラリでは、`<yvals_core.h>` という独自ヘッダに標準ライブラリの機能テストマクロがまとめられていましたが、C++20 以降ではあらゆる実装において、`<version>` ヘッダを見ることで、こうした実装固有の情報にアクセスできるため利便性が高まります。

Expand All @@ -543,9 +724,55 @@ size_type find(const T& t, size_type pos = 0) const noexcept(is_nothrow_converti
```

### ポインタライクなオブジェクトからアドレスを取得する `std::to_address()` 関数 [(P0653R2)](https://wg21.link/P0653R2)

ポインタライクなオブジェクトを引数にとり、それが表すのと同じアドレスを生ポインタで返す関数 `std::to_address(p)` が追加されます。オブジェクトがポインタ型の場合はその値を返し、それ以外の場合、`std::pointer_traits<Ptr>::to_address(p)` の特殊化が定義されていて使えればその戻り値を、そうでない場合は `std::to_address(p.operator->())` の戻り値を返します。

### `<complex>` ヘッダの関数の `constexpr` 対応を強化 [(P0415R1)](https://wg21.link/P0415R1)

### `<complex>` ヘッダの関数の `constexpr` 対応を強化 [(P0415R1)](https://wg21.link/P0415R1)
`<complex>` ヘッダが提供する関数のうち、複素数の四則演算、ノルムの取得、共役複素数の取得など、`constexpr` 非対応の数学関数 (sqrt など) を使わずに実装できるものが `constexpr` 化されます。


### コンパイル時評価の文脈か実行時評価の文脈かを判別できる `std::is_constant_evaluated()` 関数 [(P0595R2)](https://wg21.link/P0595)
C++17 までは、実行するコードを、コンパイル時評価か実行時評価かに応じて使い分ける方法はありませんでした。C++20 では、コンパイル時評価されている文脈では `true` を、それ以外の場合では `false` を返す `std::is_constant_evaluated()` 関数が `<type_traits>` ヘッダに追加されます。例えば標準ライブラリで `constexpr` 対応していないような数学関数を提供する際、コンパイル時評価では `constexpr` 版の実装を、実行時には非 `constexpr` の標準ライブラリの実装を提供するよう選択させることができます。なお、`std::is_constant_evaluated()` を `if constexpr` の `( )` 内や `static_assert` 内で使うと常に `true` に評価されてしまうので注意が必要です。基本的には `if (std::is_constant_evaluated())` と書きます。

```cpp
#include <cmath>
#include <type_traits>
#include <iostream>
#include <iomanip>

constexpr float Sin_impl(float x2, int i, int k, float xn, long long nf)
{
return (i > 10) ? 0.0f : (k * xn / nf + Sin_impl(x2, i + 2, -k, xn * x2, nf * (i + 1) * (i + 2)));
}

constexpr float Sin(float x)
{
if (std::is_constant_evaluated())
{
return Sin_impl(x * x, 1, 1, x, 1);
}
else
{
return std::sin(x);
}
}

int main()
{
constexpr float Pi = 3.14159265f;
constexpr float theta = Pi / 4.0;

constexpr float x1 = Sin(theta); // コンパイル時計算
float x2 = Sin(theta); // 実行時計算

std::cout << std::setprecision(16);
std::cout << x1 << '\n';
std::cout << x2 << '\n';
}
```
出力
```
0.7071068286895752
0.7071067690849304
```