C++ 17 選擇述句與初始化述句

C++ 98 程式語言標準將 if、switch、while 與 for 述句的文法依序定義為:

if (condition) statement
if (condition) statement else statement
switch (condition) statement
while (condition) statement
for (for-init-statement conditionopt; expression) statement

其中,我們能在 condition 宣告一個變數[1],並使用此變數的數值控制執行流程。例如:

#include <cstdlib>
#include <ctime>
#include <iostream>

int main() {
  std::srand(std::time(NULL));

  if (int a = std::rand() % 2) {
    std::cout << "then-block: a=" << a << std::endl;
  } else {
    std::cout << "else-block: a=" << a << std::endl;
  }

  switch (int a = std::rand() % 4) {
  case 0:
  case 2:
    std::cout << "even: a=" << a << std::endl;
    break;
  case 1:
  case 3:
    std::cout << "odd: a=" << a << std::endl;
    break;
  }

  while (int a = std::rand() % 5) {
    std::cout << "while: a=" << a << std::endl;
  }

  for (int x = 5; int a = std::rand() % x; --x) {
    std::cout << "for: a=" << a << " x=" << x << std::endl;
  }
}

condition 宣告的變數的生命週期涵蓋整個述句(包含子述句)。舉例來說,上面例子中的 if 判斷式與下面程式碼相似:

int main() {
  // ...

  {
    int a = std::rand() % 2;
    if (a) {
      std::cout << "then-block: a=" << a << std::endl;
    } else {
      std::cout << "else-block: a=" << a << std::endl;
    }
  }

  // ...
}

不過兩者還是有細微的差異。如果在 condition 宣告變數,我們就不能在子述句宣告同名變數:

int main() {
  // ...

  if (int a = std::rand() % 2) {
    int a = 10;  // compilation error
  } else {
    int a = 5;  // compilation error
  }
}

C++ 17 程式語言標準讓 if 述句與 switch 述句能多一個初始化述句(init-statement):

if (init-statementopt condition) statement
if (init-statementopt condition) statement else statement
switch (init-statementopt condition) statement

這讓我們能在宣告變數的時候使用「不同於變數本身的條件」來控制執行流程。例如:以下的範例我們使用 init-statement 宣告變數用以獲取 m.emplace("hello") 的回傳值,再使用 p.secondinserted 控制執行流程:

#include <iostream>
#include <set>

int main() {
  std::set<std::string> m;

  if (auto p = m.emplace("hello"); p.second) {
    std::cout << "inserted item: " << *p.first << std::endl;
  } else {
    std::cout << "ignored repeated item: " << *p.first << std::endl;
  }

  if (auto &&[iter, inserted] = m.emplace("hello"); inserted) {
    std::cout << "inserted item: " << *iter << std::endl;
  } else {
    std::cout << "ignored repeated item: " << *iter << std::endl;
  }
}

這個寫法也能和 std::optional 結合:

#include <iostream>
#include <optional>

extern std::optional<int> get_some_value();

int main() {
  if (auto opt = get_some_value(); !opt.has_value()) {
    std::cout << "Failed to get value" << std::endl;
  } else {
    std::cout << "Got: " << *opt << std::endl;
  }
}

甚至能與 std::scoped_lock 或者 std::lock_guard 結合:

#include <iostream>
#include <mutex>

int value = 0;
std::mutex value_mutex;

int main() {
  if (std::scoped_lock l(value_mutex); value <= 5) {
    ++value;
  } else {
    --value;
  }

  if (std::lock_guard<std::mutex> l(value_mutex); value <= 5) {
    ++value;
  } else {
    --value;
  }

  std::cout << value << std::endl;
}

參考資料


[1]do-while 述句和其他述句不同。其文法為 do statement while (expression);。do-while 述句是以 expression(而不是 condition)控制執行流程,故不能宣告變數。

Note

如果你喜歡這篇文章,請追蹤羅根學習筆記粉絲專頁。最新文章都會在粉絲專頁發佈通知。