C++17 - コア言語の詳細

記事「C++17 - コア言語の新機能」で新しい C++17 コア言語の全体像を説明した後、今日はさらに詳細を説明します。詳細は、主にインライン変数、テンプレート、auto による自動型推定、および属性に関するものです。

これが C++17 の全体像です。

しかし、あまり知られていない機能について書かせてください。

インライン変数

インライン変数のおかげで、C++ コードをヘッダーのみのライブラリとしてパッケージ化しない主な理由はなくなりました。グローバル変数と静的変数をインラインで宣言できます。インライン関数に適用されるのと同じ規則がインライン変数に適用されます。

つまり:

  • インライン変数には複数の定義がある場合があります。
  • インライン変数の定義は、それが使用される翻訳単位に存在する必要があります。
  • グローバル インライン変数 (非静的インライン変数) は、すべての翻訳単位でインラインで宣言する必要があり、すべての翻訳単位で同じアドレスを持ちます。

繰り返しますが、インライン変数の大きな利点です。変数をヘッダー ファイルに直接入れて、それらを複数回含めることができます。

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

class Widget{
 public:
 Widget() = default;
 Widget(int w): width(w), height(getHeight(w)){}
 Widget(int w, int h): width(w), height(h){}

 private:
 int getHeight(int w){ return w*3/4; }
 static inline int width= 640;
 static inline int height= 480;
 static inline bool frame= false;
 static inline bool visible= true;

 ...
};

inline Widget wVGA;

auto は、初期化子から変数の型を自動的に推測できます。オートの話は続きます。 auto のおかげで、関数テンプレートとコンストラクター (「C++17 - コア言語の新機能」を参照) のテンプレート パラメーターはその引数から自動的に推測でき、非型のテンプレート パラメーターはそのテンプレート引数から自動的に推測できます。私の文の最後の部分にイライラしますか?イライラしている?それはいいです。文の最後の部分については、次の章で書きます。

非型テンプレート パラメータの自動型推定

初めに。非型テンプレート パラメータとはこれらは、nullptr、整数、左辺値参照、ポインター、および列挙型です。この投稿では、主に整数型について言及します。

多くの理論を説明した後、例から始めましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
template <auto N>
class MyClass{
 ...
};

template <int N> 
class MyClass<N> {
 ...
};


MyClass<'x'> myClass2; // Primary template for char
MyClass<2017> myClass1; // Partial specialisation for int

テンプレート シグネチャの 1 行目で auto を使用すると、N は非型のテンプレート パラメーターになります。コンパイラは自動的にそれを推測します。 int の部分的な特殊化も可能です:6 行目と 7 行目。12 行目のテンプレートのインスタンス化では、プライマリ テンプレート (1 ~ 4 行目) が使用され、次のテンプレートのインスタンス化では int の部分的な特殊化が使用されます。

通常の型修飾子を使用して、非型テンプレート パラメーターの型を制約できます。したがって、部分的な特殊化を使用する必要はありません。

template <const auto* p> 
struct S;

ここで、p は const へのポインターでなければなりません。

非型テンプレート パラメーターの自動型推定は、可変個引数テンプレートでも機能します。

1
2
3
4
5
6
7
8
9
template <auto... ns>
class VariadicTemplate{
 ...
};

template <auto n1, decltype(n1)... ns>
class TypedVariadicTemplate{
 ...
};

したがって、1 ~ 4 行目の VariadicTemplate は、任意の数の非型テンプレート パラメーターを推測できます。 TypeVariadicTemplate は、最初のテンプレート パラメーターのみを推測します。残りのテンプレート パラメータは同じ型になります。

{}-Initialisation と組み合わせた auto のルールは、C++17 で変更されました。

auto と {}-Initialisation の組み合わせ

auto を {}-Initialisation と組み合わせて使用​​すると、std::initializer_list が取得されます。

 auto initA{1}; // std::initializer_list<int>
 auto initB= {2}; // std::initializer_list<int>
 auto initC{1, 2}; // std::initializer_list<int>
 auto initD= {1, 2}; // std::initializer_list<int>

それは覚えやすく、教えやすいルールでした。悲しいことに、C++17 では、私の観点からこの機能は良くありません。

 auto initA{1}; // int
 auto initB= {2}; // std::initializer_list<int>
 auto initC{1, 2}; // error, no single element
 auto initD= {1, 2}; // std::initializer_list<int>

さて、ルールはより複雑です。 {} で割り当てると、std::initializer_list が返されます。コピー構築は、単一の値に対してのみ機能します。

小さいながらも素晴らしい機能です。

ネストされた名前空間

C++17 では、ネストされた名前空間を非常に快適に定義できます。

書く代わりに

namespace A {
 namespace B {
 namespace C {
 ...
 }
 }
}

あなたは単に書くことができます:

namespace A::B::C {
 ...
}

C++17 には、[[fallthrough]]、[[nodiscard]]、および [[maybe_unused]] の 3 つの新しい属性があります。

3 つの新しい属性 fallthrough、nodiscard、maybe_unused

3 つすべてがコンパイラの警告を処理します。例は cppreference.com からのものです。

フォールスルー

[[fallthrough]] は、switch ステートメントで使用できます。ケース ラベルの直前の独自の行に配置する必要があり、フォール スルーが意図的なものであり、コンパイラの警告を診断すべきではないことを示します。

ここに小さな例があります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
void f(int n) {
 void g(), h(), i();
 switch (n) {
 case 1:
 case 2:
 g();
 [[fallthrough]];
 case 3: // no warning on fallthrough
 h();
 case 4: // compiler may warn on fallthrough
 i();
 [[fallthrough]]; // ill­formed, not before a case label
 }
}

7 行目の [[fallthrough]] 属性は、コンパイラの警告を抑制します。これは 10 行目では当てはまりません。コンパイラが警告する場合があります。 12 行目は、その後にケース ラベルがないため、形式が正しくありません。

ノーディスカード

[[nodiscard]] は、関数宣言、列挙宣言、またはクラス宣言で使用できます。 nodiscard として宣言された関数からの戻り値を破棄すると、コンパイラは警告を発行する必要があります。 nodiscard として宣言された列挙型またはクラスを返す関数についても同じことが言えます。 void へのキャストは警告を出すべきではありません。

したがって、5 行目は警告を発するはずですが、関数 foo は参照によって戻るため、10 行目は警告を発するべきではありません。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
struct [[nodiscard]] error_info { };
error_info enable_missile_safety_mode();
void launch_missiles();
void test_missiles() {
 enable_missile_safety_mode(); // compiler may warn on discarding a nodiscard value
 launch_missiles();
}
error_info& foo();
void f1() {
 foo(); // nodiscard type is not returned by value, no warning
} 

maybe_unused

[[maybe_unused]] は、クラス、typedef、変数、非静的データ メンバー、関数、列挙、または列挙子の宣言で使用できます。 Maybe_unused のおかげで、コンパイラは未使用のエンティティに関する警告を抑制します。

1
2
3
4
5
6
void f([[maybe_unused]] bool thing1,
 [[maybe_unused]] bool thing2)
{
 [[maybe_unused]] bool b = thing1;
 assert(b); // in release mode, assert is compiled out
}

リリース モードでは、5 行目がコンパイルされます。 b は Maybe_unused として宣言されているため、警告は発生しません。変数 thing2 についても同じことが言えます。

次は?

コア C++17 言語 (C++17 - コア言語の新機能) を紹介した後、この投稿で詳細を説明しました。次回の投稿も同様です。次の投稿で、新しい C++17 ライブラリの詳細を紹介します。したがって、C++17 - ライブラリの新機能に興味を持った場合に備えてください。