文法機能の強化

long long 型 (C++11)

C++11 では、 新た な 整数型 として long long 型 が 追加 さ れ て い ます。 C 言語 では 1999 年 の C 99 規格 の 時点 で 追加 さ れ て いる 型 なので、 古い コンパイラ でも 対応 し て いる 可能性 が あり ます。

int main() { 
 long long n1 = 0LL; 
 long long int n2 = 0LL; // 「int」 は あっても 無くても 同じ 
 signed long long n3 = 0LL; // 「signed」 は あっても 無くても 同じ 
 unsigned long long n4 = 0ULL; 
}

long long 型 の 整数 リテラル を 表す サフィックス は「 ll」 または「 LL」 です。 unsigned long long 型 の 場合 は「 ull」 または「 ULL」 になり ます。 long long 型 の サイズ は 64 ビット 以上 で ある こと が 保証 さ れ て い ます。

#include < iostream > 
#include < limits > 
int main() { 
 std::cout << std::numeric_ limits < long long >::min() << std::endl;
  std::cout << std::numeric_ limits < long long >::max() << std::endl; 
  std::cout << std::numeric_ limits < unsigned long long >::min() << std::endl; 
  std::cout << std::numeric_ limits < unsigned long long >::max() << std::endl; 
}

実行結果:

-9223372036854775808

9223372036854775807

0

18446744073709551615

using による型の別名定義 (C++11)

従来 から、 型 に 別名 を 与える 際 には、 typedef を 用い て き まし た。 C++11 からは、 同様 の こと を using キーワード を 用い て 行う こと が でき ます。 using を 使う 場合 の 構文 は、 以下 の よう になり ます。

before (C ++ 98/ 03)
#include < list > 
typedef unsigned int u32_t;
typedef std::list<int>::iterator lst_it;
typedef const char* (*GetTextFunc_t)( int); 
int main() {}
after (C++11/ 14/ 17)
#include < list > 
using u32_t = unsigned int;
using lst_it = std::list<int>::iterator; 
using GetTextFunc_t = const char*(*)(int);

typedef とは 逆 に、 まず 新しい 型 の 名前 を 書き、「=」 で 区切っ て、 既存 の 型名 を 書き ます。 区切り が 明確 です し、 変数 を 宣言 し て 初期 値 を 与える 際 の 形 と 同じ 形 を し て い て、 意味 も 分かり やすく なり ます。 例えば、 1 個 目 の 型 定義 は、「 u32_t という 名前 の 型 を 宣言 し、 それ は unsigned int の 別名 で ある」 という よう に 簡単 に 読み 下せ ます。 関数 ポインタ 型 の 別名 定義 も、 他 と 同じ 形 で 書ける よう に なっ て おり、 読み やすく なっ て い ます。

範囲for文 (C++11、 C++17)

C++11 では、 従来 の for 文 に 加え て、 範囲 for 文 という 記法 が 使える よう になり まし た。 範囲 for 文 は、 配列 や コンテナ の よう な 要素 の 集まり を、 先頭 から 末尾 に 向かっ て 列挙 し て いく 際 に 使え ます。 構文 は 次 の よう になり ます。

for (型 名前 : 要素の集まり) 処理

繰り返し の たび に 要素 が 1つ 取り出さ れ て、「 型 名前」 の 部分 に 記述 し た( 宣言 し た) 変数 に 代入 さ れ ます。 これ は 要素 の コピー なので、 ループ の 中 で、 元 の 要素 を 書き換え たい 場合 は、 参照 で 受け取る よう に し ます。

before (C++98/ 03)
#include < iostream > 
#include < list > 
int main() { 
 int array[5] = {0, 1, 2, 3, 4}; 
 std:: list < int > lst( array, array + 5); 
 for (int i = 0; i < 5; ++ i) { 
  array[i] += 10;
 } 
 for (std:: list < int >:: iterator it = lst.begin(); it != lst.end(); ++ it) {
  *it += 10; 
 } 
 for (int i = 0; i < 5; ++ i) { 
  std:: cout << array[i] << "\n"; 
 } 
 for (std:: list < int >:: const_iterator it = lst.begin(); it != lst.end(); ++ it) {
   std:: cout << *it << "\n"; 
 } 
}

実行結果:10 11 12 13 14 10 11 12 13 14

after (C++11/ 14/ 17) 範囲 for 文を使う場合、参照を用いて書きます。
#include < iostream > 
#include < list > 
int main() { 
  int array[5] = {0, 1, 2, 3, 4};
  std:: list < int > lst( array, array + 5); 
  for (auto& n : array) {
    n += 10; 
  } 
  for (auto& n : lst) { 
    n += 10; 
  } 
  for (auto n : array) { 
    std:: cout << n << "\n"; 
  } 
  for (auto n : lst) { 
    std:: cout << n << "\n"; 
  } 
}

実行結果:10 11 12 13 14 10 11 12 13 14

コード量が大幅に削減されることが分かります。 簡潔に書けることで楽ができますし、間違いが入り込む余地も減ります。また、添字やイテレータがなくなり、配列とコンテナの違いも消え去りました。配列の要素数を書かなくなったのも、コードの重複が減り、間違いも減らせるので良いポイントです。

名前空間の入れ子構文 (C++17)

C++17 から、名前空間の中にある名前空間を定義するとき、これまでよりも簡潔な記述が可能になりました。例えば、Aという名前空間の中に、Bという名前空間があるとすると、次のように記述できます。

namespace A::B { }

これは以下と同等です。

namespace A { 
  namespace B {
  }
}

if、switchの条件式と初期化の分離 (C++17)

C++17 から、if文とswitch文の ( ) の中に初期化文を置くことができるようになりまし た。

int getResult() { 
 return 1;
} 
int main() { 
 if (auto result = getResult(); result > 0) {} 
 switch (auto result = getResult(); result) { 
  case -1: break; 
  case 0: break; 
  default: break; 
 } 
}

before (C++98/ 03/ 11/ 14)

変数 の スコープ を 小さく 保つ ため、 無駄 な { } を 補う 方法 を 使う こと が あり まし た。

enum Status { // C ++ 11 以降 なら Scoped Enum にすべし 
 STATUS_ OK, 
 STATUS_ ERROR, 
}; 
Status getStatus() { 
 return STATUS_ OK; 
} 
int main() {
 { 
  const Status status = getStatus(); 
  if (status == STATUS_ ERROR) {} 
 }  
 { 
  const Status status = getStatus(); 
  switch (status) { 
   case STATUS_ OK: break; 
   case STATUS_ ERROR: break; 
  } 
 } 
}

余分な {} を補うのは、面倒であるばかりか、忘れやすくもあります。余計なネストが発生する点でも、あまり気持ちの良いものではありませんでし た。

after (C++17)
enum class Status { // Scoped Enum 
 OK, 
 ERROR, 
}; 
Status getStatus() { 
 return Status::OK; 
} 
int main() { 
 if (const auto status = getStatus(); status == Status::ERROR) {} 
 switch (const auto status = getStatus(); status) { 
  case Status::OK: break;
  case Status:: ERROR: break; 
 } 
}

構造化束縛 (C++17)

C++17 で 追加 さ れ た 構造 化 束縛 は、 配列 や 構造 体 の よう に、 複数 の 値 を 持っ た 値( 多値) を、 1つ 1つ に 分解 し て、 別個 の 変数 で 受け取る 機能 です。 変数 宣言 と、 多値 の 分解 を 同時に同時に 行える 機能 で ある と 考え られ ます。 構造 化 束縛 は auto [変数 名, …] = 多値 という 形 で 記述 し ます。 これ は、 変数 宣言 の 文法 の 一種 です。 変数 を 宣言 し つつ、 初期 値 を 与え て いる の だ と 理解 し て 下さい。 多値 を 分解 し た 値 が、 変数 の 初期 値 として 使用 さ れ ます。 変数 の 型 は 常に「 auto」 を 指定 し ます。 多 値 側 の 値 の 型 が、 宣言 さ れ た 各 変数 の 型 となり ます。 この とき の 型 の 決定 方法 は、 auto と いう よりも、 むしろ decltype の ルール と 合致 し ます。 つまり、 多 値 側 の 型 が 参照 型 で あれ ば、 宣言 する 変数 の 型 は きちんと 参照 型 になり ます。 多値 に 含ま れる 値 の 個数 と、 宣言 側 の 変数 の 個数 は 一致 し て い なけれ ば なり ませ ん。

配列を分解する

次のプログラムは、配列 array の各要素を分解して、別個の変数を初期化しています。

#include < iostream > 
#include < string > 
int main() { 
 const int array[] = {1, 2, 3};
 auto [a, b, c] = array; // a、 b、 c はいずれも int 
 std::cout << a << "\n" << b << "\n" << c << std::endl;
}

実行結果: 1 2 3

auto [a, b, c] = array; は、変数 a, b, c をそれぞれ宣言し、初期値として array[0]、array[1]、 array[2] の値を与えています。

構造体を分解する

次のプログラムは、構造体 Data の各メンバを分解して別個の変数を初期化してい ます。

#include < iostream > 
#include < string >
int main() { 
 struct Data { 
  int x; 
  double y; 
  std::string z; 
 }; 
 const Data data = {10, 3.5, "???"}; 
 auto [x, y, z] = data; // x は int、 y は double、 z は std::string 
 std::cout << x << "\n" << y << "\n" << z << std::endl; 
}

実行結果:10 3. 5 ???

変数の型の修飾

構造化束縛の変数宣言型の型「 auto」 には、参照を表す「&」や、 const を付加することができます。これらの効果は、宣言するすべての変数に適用されます。

#include < iostream > 
#include < string > 
int main() { 
 struct Data { 
  int x; 
  double y; 
  const std:: string& z; 
 }; 
 Data data = {10, 3. 5, "???"}; 
 auto [x, y, z] = data; // x は int、 y は double、 z は const std::string& 
 auto& [rx, ry, rz] = data; // rx は int&、 ry は double&、 rz は const std::string&
 data. x = 999; 
 std::cout << x << "\n" << y << "\n" << z << std::endl; 
 std::cout << rx << "\n" << ry << "\n" << rz << std::endl; 
 const auto [cx, cy, cz] = data; 
 // cx は const int、 cy は const double、 cz は const std::string& 
 // cy = 0. 0; 
 // cy は const double なので 書き換え られ ない 
}

実行結果: 10 3. 5 ??? 999 3. 5 ???

std::pair に対する構造化束縛

構造化束縛 は、std::pair の 要素 を 分解 する こと にも 使用 でき ます。

#include < iostream > 
#include < utility > 
int main() { 
  const auto x = std::make_ pair( 10, false); // x は std::pair < int, bool > 
  auto [value, flag] = x; // value は int、 flag は bool 
  std::cout << value << "\n" << flag << std::endl; 
}

実行結果: 10 0

std::pair は、std::map がキー と値を表現する方法としても使われていますから、std::map と構造 化束縛を組み合わせて使うこともあります。例えば、範囲 for文を使って、std::map の内容を走査する処理を、次のように記述できます。

#include < iostream > 
#include < map > 
#include < string > 
int main() { 
  std::map < std::string, int > m = {
    {"aaa", 10}, 
    {"bbb", 20}, 
    {"ccc", 30}, 
  };
  for (const auto& [key, value] : m) { // key は const std::string&、 value は const int& 
    std::cout << key << "," << value << std::endl; 
  } 
}

実行結果: aaa, 10 bbb, 20 ccc, 30

std::map の mを範囲 for 文 で使用すると、std::map の begin() と end() の 範囲 で 要素 が 走査 さ れ ます。 ループ の たび に 各 要素 を 変数 に 受け取り ます が、 std::map の 要素 は std::pair に なっ て いる ので、 構造 化 束縛 で 分解 する こと が でき ます。 構造 化 束縛 の 記法 を、 範囲 for 文 の 変数 宣言 の ところ に 書け ば 良い です( 前 に 述べ た 通り、 構造 化 束縛 は 変数 宣言 の 文法 の 一種 です)。

コメント

タイトルとURLをコピーしました