C++17 - コア言語の新機能

C++11、C++14、および C++17。私は、あなたがパターンを見ると思います。今年後半に、新しい C++ 標準を入手する予定です。 2017 年 3 月、C++17 仕様はドラフト国際標準段階に達しました。詳細に入る前に、C++17 の概要を説明します。

まず全体像を見てみましょう。

全体像

C++98 から C++14 については、大きなポイントだけを述べました。しかし、私のグラフィックには C++ 標準 C++03 がありません。 C++03 は非常に小さな C++ 標準であるため、これは意図的なものです。 C++98 のバグ修正リリースのようなものです。 C++ の知識があれば、最初の ISO 標準である C++98 と ISO 標準である C++11 が大きな標準であることをご存知でしょう。これは C++14、特に C++03 には当てはまりません。

そこで質問です。 C++17 は大きな C++ 標準ですか、それとも小さな標準ですか?私の観点から言えば、答えは非常に簡単です。 C++17 は、C++14 と C++11 の間のようなものです。したがって、C++17 は大きくも小さくもありません。なんで?これが私の短い答えです。

概要

C++17 には多くの機能があります。それはコア言語とライブラリにも当てはまります。まずコア言語を見てみましょう。

コア言語

折りたたみ式

C++11 は可変個引数テンプレートをサポートしています。これらは、任意の数の引数を受け入れることができるテンプレートです。任意の数はパラメータパックで保持されます。さらに、C++17 では、二項演算子を使用してパラメーター パックを直接削減できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// foldExpressionAll.cpp

#include <iostream>

template<typename... Args>
bool all(Args... args) { return (... && args); }

int main(){

 std::cout << std::boolalpha;

 std::cout << "all(): " << all() << std::endl;
 std::cout << "all(true): " << all(true) << std::endl;
 std::cout << "all(true, true, true, false): " << all(true, true, true, false) << std::endl;

 std::cout << std::endl;

}

二項演算子は、6 行目の論理 AND です。これがプログラムの出力です。

フォールド式については、既に記事を書いているので、ここまでです。詳細は以上です。

コンパイル時のままです。

constexpr if

constexpr は、条件付きでソース コードをコンパイルできるようにします。

1
2
3
4
5
6
7
template <typename T>
auto get_value(T t) {
 if constexpr (std::is_pointer_v<T>)
 return *t; // deduces return type to int for T = int*
 else
 return t; // deduces return type to int for T = int
}

T がポインターの場合、3 行目の if 分岐がコンパイルされます。そうでない場合、else は 5 行目で分岐します。重要な点が 2 つあります。関数 get_value には 2 つの異なる戻り値の型があり、ステートメントが有効でなければならない場合の両方の分岐があります。

したがって、for ステートメントで可能なことは、if および switch ステートメントで可能な C++17 です。

if ステートメントと switch ステートメントの初期化子

if および switch ステートメント内で変数を直接初期化できます。

1
2
3
4
5
6
7
8
9
std::map<int,std::string> myMap;

if (auto result = myMap.insert(value); result.second){
 useResult(result.first); 
 // ...
} 
else{
 // ...
} // result is automatically destroyed

したがって、変数 result は、if ステートメントの if および else ブランチ内で有効です。しかし、結果は外側のスコープを汚染しません.

if ステートメントと switch ステートメントで初期化子を構造化バインディング宣言と組み合わせて使用​​すると、C++ 構文がより洗練されたものになります。

構造化バインディング宣言

構造化バインディングのおかげで、std::tuple または構造体を変数に直接バインドできます。したがって、最後の例をさらに改善できます。

1
2
3
4
5
6
7
8
9
std::map<int,std::string> myMap;
 
if (auto [iter, succeeded] = myMap.insert(value); succeeded) {
 useIter(iter); 
 // ...
}
else{
 // ...
} iter and succeded are automatically be destroyed

3 行目の auto [iter, successfully] は、2 つの変数 iter と successed を自動的に作成します。それらは 9 行目で破棄されます。

プログラミングの煩わしさを軽減するこれらの機能の 1 つ。同じことがコンストラクターのテンプレート推定にも当てはまります。

コンストラクターのテンプレート推定

関数テンプレートは、関数の引数から型パラメーターを推定できます。しかし、それは特別な関数テンプレート、つまりクラス テンプレートのコンストラクターでは不可能でした。 C++17 では、このステートメントは単純に間違っています。コンストラクターは、コンストラクターの引数から型パラメーターを推測できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// templateArgumentDeduction.cpp

#include <iostream>

template <typename T>
void showMe(const T& t){
 std::cout << t << std::endl;
}

template <typename T>
struct ShowMe{
 ShowMe(const T& t){
 std::cout << t << std::endl;
 }
};

int main(){
 
 std::cout << std::endl;
 
 showMe(5.5); // not showMe<double>(5.5);
 showMe(5); // not showMe<int>(5);
 
 ShowMe<double>(5.5); // with C++17: ShowMe(5.5);
 ShowMe<int>(5); // with C++17: ShowMe(5);
 
 std::cout << std::endl;
 
}

行 11 と行 22 は、最初の C++ 標準以降、C++ で可能です。 24行目と25行目はC++17で可能になります。したがって、山括弧を使用してクラス テンプレートをインスタンス化する必要はありません。

使いやすさだけではありません。さらに、パフォーマンス機能も取得します。

保証付きコピー省略

RVO R の略 V を返す アルエ O 最適化とは、コンパイラが不要なコピー操作を削除できるようにすることを意味します。これまで可能な最適化ステップだったことが、C++17 では保証されます。

1
2
3
4
MyType func(){
 return MyType{}; // no copy with C++17
}
MyType myType = func(); // no copy with C++17

これらの数行で 2 つの不要なコピー操作が発生する可能性があります。行 2 の最初のものと行 4 の 2 番目のもの。C++17 では、両方のコピー操作を実行する必要があります。

戻り値に名前がある場合、NRVO と呼びます。 たぶん、あなたはそれを推測しました。この頭字語は Nの略です アメッド R V を返す アリュー O 最適化。

1
2
3
4
5
MyType func(){
 MyType myVal;
 return myVal; // one copy allowed 
}
MyType myType = func(); // no copy with C++17

微妙な違いは、コンパイラが C++17 に従って値 myValue を引き続きコピーできることです (3 行目)。ただし、5 行目ではコピーは行われません。

機能が不要になった場合、またはそのアプリケーションが危険でさえある場合は、削除する必要があります。これは、C++17 で std::auto_ptr と trigraphs を使用して発生します。

auto_ptr とトライグラフを削除

auto_ptr

std::auto_ptr は、C++ の最初のスマート ポインターです。その仕事は、1 つのリソースを処理することです。しかし、それには大きな問題がありました。 std::auto_ptr をコピーすると、内部で移動操作が行われます。これが理由です。代わりに C++11 を使用して std::unique_ptr を取得します。 std::unique_ptr をコピーすることはできません。

1
2
3
4
5
6
std::auto_ptr<int> ap1(new int(2011));
std::auto_ptr<int> ap2= ap1; // OK (1)

std::unique_ptr<int> up1(new int(2011));
std::unique_ptr<int> up2= up1; // ERROR (2)
std::unique_ptr<int> up3= std::move(up1); // OK (3)

トライグラフ

Trigraph は、ソース コード内の 3 文字のシーケンスであり、1 文字のように扱われます。キーボードが単一の文字をサポートしていない場合に必要になります。

難読化されたコードを書きたい場合、C++17 はもはやあなたの言語ではないかもしれません。

1
2
3
4
5
6
7
// trigraphs.cpp

int main()??<

 ??(??)??<??>();

??>

プログラムは何をしているのでしょう?そうでない場合は、トリグラフを 1 文字表現に変換する必要があります。

表を当てはめれば、なぞなぞが解けます。このプログラムは、ちょうどその場で実行されるラムダ関数を表しています。

1
2
3
4
5
6
7
// trigraphsLambda.cpp

int main(){

 []{}();

}

次は?

それは簡単だ。次の投稿では、C++17 で得られるライブラリ機能について書きます。これらは、string_view、並列 STL、およびファイルシステム ライブラリです。さらに、新しいデータ型 std::any、std::optional、および std::variant を取得します。