From efc33d7a4b03959b2499610c3f04142e28263271 Mon Sep 17 00:00:00 2001 From: smdn Date: Sun, 5 Jul 2026 12:17:31 +0900 Subject: [PATCH 1/5] use std::print instead of std::ostream --- src/impls/cpp/Makefile | 2 +- src/impls/cpp/polish.cpp | 60 ++++++++++++++++++------------------ src/impls/cpp/polish.vcxproj | 4 +-- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/impls/cpp/Makefile b/src/impls/cpp/Makefile index 3b9767c..7bfa4d7 100644 --- a/src/impls/cpp/Makefile +++ b/src/impls/cpp/Makefile @@ -1,6 +1,6 @@ CXX = g++ #CXX = clang++ -CXXFLAGS = -std=c++2a -O2 -Wall +CXXFLAGS = -std=c++23 -O2 -Wall all: polish diff --git a/src/impls/cpp/polish.cpp b/src/impls/cpp/polish.cpp index bf7ffca..327ac95 100644 --- a/src/impls/cpp/polish.cpp +++ b/src/impls/cpp/polish.cpp @@ -7,8 +7,8 @@ #include #include #include -#include #include +#include #include #include @@ -35,15 +35,15 @@ class Node { // 後行順序訪問(帰りがけ順)で二分木を巡回して // すべてのノードの演算子または項をstreamに出力するメソッド - void write_postorder(std::ostream& stream); + void write_postorder(std::FILE* stream); // 中間順序訪問(通りがけ順)で二分木を巡回して // すべてのノードの演算子または項をstreamに出力するメソッド - void write_inorder(std::ostream& stream); + void write_inorder(std::FILE* stream); // 先行順序訪問(行きがけ順)で二分木を巡回して // すべてのノードの演算子または項をstreamに出力するメソッド - void write_preorder(std::ostream& stream); + void write_preorder(std::FILE* stream); // 後行順序訪問(帰りがけ順)で二分木を巡回して、二分木全体の値を計算するメソッド // すべてのノードの値が計算できた場合はtrue、そうでない場合(記号を含む場合など)はfalseを返す @@ -282,7 +282,7 @@ void Node::traverse( on_leave(*this); } -void Node::write_postorder(std::ostream& stream) +void Node::write_postorder(std::FILE* const stream) { // 巡回を開始する traverse( @@ -290,11 +290,11 @@ void Node::write_postorder(std::ostream& stream) nullptr, // ノードの通りがけには何もしない // ノードからの帰りがけに、ノードの演算子または項を出力する // (読みやすさのために項の後に空白を補って出力する) - [&stream](Node& node) { stream << node.expression << ' '; } + [&stream](Node& node) { std::print(stream, "{} ", node.expression); } ); } -void Node::write_inorder(std::ostream& stream) +void Node::write_inorder(std::FILE* const stream) { // 巡回を開始する traverse( @@ -302,37 +302,37 @@ void Node::write_inorder(std::ostream& stream) [&stream](Node& node) { // 左右に項を持つ場合、読みやすさのために項の前(行きがけ)に開き括弧を補う if (node.left && node.right) - stream << '('; + std::print(stream, "("); }, // ノードの通りがけに、ノードの演算子または項を出力する [&stream](Node& node) { // 左に子ノードを持つ場合は、読みやすさのために空白を補う if (node.left) - stream << ' '; + std::print(stream, " "); // 左の子ノードから右の子ノードへ巡回する際に、ノードの演算子または項を出力する - stream << node.expression; + std::print(stream, "{}", node.expression); // 右に子ノードを持つ場合は、読みやすさのために空白を補う if (node.right) - stream << ' '; + std::print(stream, " "); }, // ノードからの帰りがけに、必要なら閉じ括弧を補う [&stream](Node& node) { // 左右に項を持つ場合、読みやすさのために項の後(帰りがけ)に閉じ括弧を補う if (node.left && node.right) - stream << ')'; + std::print(stream, ")"); } ); } -void Node::write_preorder(std::ostream& stream) +void Node::write_preorder(std::FILE* const stream) { // 巡回を開始する traverse( // ノードへの行きがけに、ノードの演算子または項を出力する // (読みやすさのために項の後に空白を補って出力する) - [&stream](Node& node) { stream << node.expression << ' '; }, + [&stream](Node& node) { std::print(stream, "{} ", node.expression); }, nullptr, // ノードの通りがけ時には何もしない nullptr // ノードからの帰りがけ時には何もしない ); @@ -425,7 +425,7 @@ std::string Node::format_number(const double& number) noexcept // 2: 計算のエラーによる終了 (式全体の値の計算に失敗した場合) int main() { - std::cout << "input expression: "; + std::print("input expression: "); // 標準入力から二分木に分割したい式を入力する std::string expression; @@ -450,44 +450,44 @@ int main() // 二分木の根(root)ノードを作成し、式全体を格納する root = std::make_unique(expression); - std::cout << "expression: " << expression << std::endl; + std::println("expression: {}", expression); // 根ノードに格納した式を二分木へと分割する root->parse_expression(); } catch (const MalformedExpressionException& err) { - std::cerr << err.what() << std::endl; + std::println(stderr, "{}", err.what()); return 1; } // 分割した二分木を帰りがけ順で巡回して表示する(前置記法/逆ポーランド記法で表示される) - std::cout << "reverse polish notation: "; - root->write_postorder(std::cout); - std::cout << std::endl; + std::print("reverse polish notation: "); + root->write_postorder(stdout); + std::println(); // 分割した二分木を通りがけ順で巡回して表示する(中置記法で表示される) - std::cout << "infix notation: "; - root->write_inorder(std::cout); - std::cout << std::endl; + std::print("infix notation: "); + root->write_inorder(stdout); + std::println(); // 分割した二分木を行きがけ順で巡回して表示する(後置記法/ポーランド記法で表示される) - std::cout << "polish notation: "; - root->write_preorder(std::cout); - std::cout << std::endl; + std::print("polish notation: "); + root->write_preorder(stdout); + std::println(); // 分割した二分木から式全体の値を計算する double result_value; if (root->calculate_expression_tree(result_value)) { // 計算できた場合はその値を表示する - std::cout << "calculated result: " << Node::format_number(result_value) << std::endl; + std::println("calculated result: {}", Node::format_number(result_value)); return 0; } else { // (式の一部あるいは全部が)計算できなかった場合は、計算結果の式を中置記法で表示する - std::cout << "calculated expression: "; - root->write_inorder(std::cout); - std::cout << std::endl; + std::print("calculated expression: "); + root->write_inorder(stdout); + std::println(); return 2; } } diff --git a/src/impls/cpp/polish.vcxproj b/src/impls/cpp/polish.vcxproj index 73b1af4..937a3a4 100644 --- a/src/impls/cpp/polish.vcxproj +++ b/src/impls/cpp/polish.vcxproj @@ -1,4 +1,4 @@ - @@ -19,7 +19,7 @@ SPDX-License-Identifier: MIT - stdcpp20 + stdcpp23 /utf-8 Level3 From dcb7589e8b52a52d30479e94fd62a4c3a9ff22f0 Mon Sep 17 00:00:00 2001 From: smdn Date: Sun, 5 Jul 2026 12:19:53 +0900 Subject: [PATCH 2/5] use std::format to convert numbers with format --- src/impls/cpp/polish.cpp | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/src/impls/cpp/polish.cpp b/src/impls/cpp/polish.cpp index 327ac95..8cbaa4b 100644 --- a/src/impls/cpp/polish.cpp +++ b/src/impls/cpp/polish.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -50,9 +49,6 @@ class Node { // 計算結果はresult_valueに代入する bool calculate_expression_tree(double& result_value); - // 演算結果の数値を文字列化するためのメソッド - static std::string format_number(const double& number) noexcept; - private: // 式expression内の括弧の対応を検証するメソッド // 開き括弧と閉じ括弧が同数でない場合はエラーとする @@ -378,10 +374,10 @@ void Node::calculate_node(Node& node) // 現在のノードの演算子に応じて左右の子ノードの値を演算し、 // 演算した結果を文字列に変換して再度expressionに代入することで現在のノードの値とする switch (node.expression.front()) { - case '+': node.expression = format_number(left_operand + right_operand); break; - case '-': node.expression = format_number(left_operand - right_operand); break; - case '*': node.expression = format_number(left_operand * right_operand); break; - case '/': node.expression = format_number(left_operand / right_operand); break; + case '+': node.expression = std::format("{:.17g}", left_operand + right_operand); break; + case '-': node.expression = std::format("{:.17g}", left_operand - right_operand); break; + case '*': node.expression = std::format("{:.17g}", left_operand * right_operand); break; + case '/': node.expression = std::format("{:.17g}", left_operand / right_operand); break; // 上記以外の演算子の場合は計算できないものとして扱い、処理を終える default: return; } @@ -406,19 +402,6 @@ bool Node::parse_number(const std::string_view& expression, double& number) noex return ptr == std::to_address(std::end(expression)); } -std::string Node::format_number(const double& number) noexcept -{ - std::ostringstream stream; - - // %.17g - stream.precision(17); // %.17 - stream - << std::defaultfloat // %g - << number; - - return stream.str(); -} - // main関数。 結果によって次の値を返す。 // 0: 正常終了 (二分木への分割、および式全体の値の計算に成功した場合) // 1: 入力のエラーによる終了 (二分木への分割に失敗した場合) @@ -480,7 +463,7 @@ int main() if (root->calculate_expression_tree(result_value)) { // 計算できた場合はその値を表示する - std::println("calculated result: {}", Node::format_number(result_value)); + std::println("calculated result: {:.17g}", result_value); return 0; } else { From 17d83f9934ad6a9f2ceb00540edf7ab278f09c98 Mon Sep 17 00:00:00 2001 From: smdn Date: Sun, 5 Jul 2026 12:25:19 +0900 Subject: [PATCH 3/5] use std::invocable instead of std::function --- src/impls/cpp/polish.cpp | 45 +++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/src/impls/cpp/polish.cpp b/src/impls/cpp/polish.cpp index 8cbaa4b..35bc0f2 100644 --- a/src/impls/cpp/polish.cpp +++ b/src/impls/cpp/polish.cpp @@ -1,9 +1,9 @@ // SPDX-FileCopyrightText: 2022 smdn // SPDX-License-Identifier: MIT #include +#include #include #include -#include #include #include #include @@ -27,9 +27,9 @@ class Node { // 二分木を巡回し、ノードの行きがけ・通りがけ・帰りがけに指定された関数をコールバックするメソッド void traverse( - std::function on_visit, // ノードの行きがけにコールバックする関数 - std::function on_transit, // ノードの通りがけにコールバックする関数 - std::function on_leave // ノードの帰りがけにコールバックする関数 + std::invocable auto on_visit, // ノードの行きがけにコールバックする関数 + std::invocable auto on_transit, // ノードの通りがけにコールバックする関数 + std::invocable auto on_leave // ノードの帰りがけにコールバックする関数 ); // 後行順序訪問(帰りがけ順)で二分木を巡回して @@ -252,41 +252,38 @@ std::string::size_type Node::get_operator_position(const std::string_view& expre } void Node::traverse( - std::function on_visit, - std::function on_transit, - std::function on_leave + std::invocable auto on_visit, + std::invocable auto on_transit, + std::invocable auto on_leave ) { // このノードの行きがけに行う動作をコールバックする - if (on_visit) - on_visit(*this); + on_visit(*this); // 左に子ノードをもつ場合は、再帰的に巡回する if (left) left->traverse(on_visit, on_transit, on_leave); // このノードの通りがけに行う動作をコールバックする - if (on_transit) - on_transit(*this); + on_transit(*this); // 右に子ノードをもつ場合は、再帰的に巡回する if (right) right->traverse(on_visit, on_transit, on_leave); // このノードの帰りがけに行う動作をコールバックする - if (on_leave) - on_leave(*this); + on_leave(*this); } void Node::write_postorder(std::FILE* const stream) { // 巡回を開始する traverse( - nullptr, // ノードへの行きがけには何もしない - nullptr, // ノードの通りがけには何もしない + [](const Node&) {}, // ノードへの行きがけには何もしない + [](const Node&) {}, // ノードの通りがけには何もしない // ノードからの帰りがけに、ノードの演算子または項を出力する // (読みやすさのために項の後に空白を補って出力する) - [&stream](Node& node) { std::print(stream, "{} ", node.expression); } + [&stream](const Node& node) { std::print(stream, "{} ", node.expression); } ); } @@ -295,13 +292,13 @@ void Node::write_inorder(std::FILE* const stream) // 巡回を開始する traverse( // ノードへの行きがけに、必要なら開き括弧を補う - [&stream](Node& node) { + [&stream](const Node& node) { // 左右に項を持つ場合、読みやすさのために項の前(行きがけ)に開き括弧を補う if (node.left && node.right) std::print(stream, "("); }, // ノードの通りがけに、ノードの演算子または項を出力する - [&stream](Node& node) { + [&stream](const Node& node) { // 左に子ノードを持つ場合は、読みやすさのために空白を補う if (node.left) std::print(stream, " "); @@ -314,7 +311,7 @@ void Node::write_inorder(std::FILE* const stream) std::print(stream, " "); }, // ノードからの帰りがけに、必要なら閉じ括弧を補う - [&stream](Node& node) { + [&stream](const Node& node) { // 左右に項を持つ場合、読みやすさのために項の後(帰りがけ)に閉じ括弧を補う if (node.left && node.right) std::print(stream, ")"); @@ -328,9 +325,9 @@ void Node::write_preorder(std::FILE* const stream) traverse( // ノードへの行きがけに、ノードの演算子または項を出力する // (読みやすさのために項の後に空白を補って出力する) - [&stream](Node& node) { std::print(stream, "{} ", node.expression); }, - nullptr, // ノードの通りがけ時には何もしない - nullptr // ノードからの帰りがけ時には何もしない + [&stream](const Node& node) { std::print(stream, "{} ", node.expression); }, + [](const Node&) {}, // ノードの通りがけ時には何もしない + [](const Node&) {} // ノードからの帰りがけ時には何もしない ); } @@ -340,8 +337,8 @@ bool Node::calculate_expression_tree(double& result_value) // ノードからの帰りがけに、ノードが表す部分式から、その値を計算する // 帰りがけに計算することによって、末端の部分木から順次計算し、再帰的に木全体の値を計算する traverse( - nullptr, // ノードへの行きがけには何もしない - nullptr, // ノードの通りがけには何もしない + [](const Node&) {}, // ノードへの行きがけには何もしない + [](const Node&) {}, // ノードの通りがけには何もしない Node::calculate_node // ノードからの帰りがけに、ノードの値を計算する ); From 14f27d7588ac37c43d057d6bcd39d7e093efc075 Mon Sep 17 00:00:00 2001 From: smdn Date: Tue, 14 Jul 2026 20:22:24 +0900 Subject: [PATCH 4/5] add compilation workaround for MinGW --- src/impls/cpp/Makefile | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/impls/cpp/Makefile b/src/impls/cpp/Makefile index 7bfa4d7..1bc5103 100644 --- a/src/impls/cpp/Makefile +++ b/src/impls/cpp/Makefile @@ -2,10 +2,18 @@ CXX = g++ #CXX = clang++ CXXFLAGS = -std=c++23 -O2 -Wall +# For MinGW (Windows), specify `-lstdc++exp` to use std::print. +# ref: https://www.reddit.com/r/cpp_questions/comments/1bsh7nd/undefined_reference_to_std_open_terminal/ +CXX_TARGET := $(shell $(CXX) -dumpmachine) + +ifneq (,$(findstring w64-mingw32,$(CXX_TARGET))) +LDFLAGS += -lstdc++exp +endif + all: polish polish: polish.o - $(CXX) polish.o -o polish + $(CXX) polish.o -o polish $(LDFLAGS) polish.o: polish.cpp $(CXX) $(CXXFLAGS) -c polish.cpp From 426efdf158be158d8d4ecc3450d3c4982dd43405 Mon Sep 17 00:00:00 2001 From: smdn Date: Fri, 11 Sep 2026 00:31:32 +0900 Subject: [PATCH 5/5] set minimum required version of g++ to 14 --- .github/workflows/run-tests-impls.yml | 27 +++++++++++++++++++++++++++ src/impls/cpp/README.md | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/.github/workflows/run-tests-impls.yml b/.github/workflows/run-tests-impls.yml index 7a223fe..22340d4 100644 --- a/.github/workflows/run-tests-impls.yml +++ b/.github/workflows/run-tests-impls.yml @@ -50,6 +50,7 @@ env: DOTNET_SDK_MINIMUM_VERSION: "10.0.100" NODEJS_MINIMUM_VERSION: 20 JAVA_MINIMUM_VERSION: 21 + GPP_MINIMUM_VERSION: 14 # cSpell:enable jobs: @@ -160,6 +161,32 @@ jobs: if: ${{ matrix.target-impl == 'python' }} run: pip install mypy + - name: Setup g++ + background: true + if: ${{ runner.os == 'Linux' && matrix.target-impl == 'cpp' }} + # Check if the current g++ version is lower than GPP_MINIMUM_VERSION. + # Automatically fall back to the compliant pre-installed g++ binary via GITHUB_PATH. + # This is achieved by creating a symlink under the $HOME directory and prioritizing it. + run: | + GPP_CURRENT_VERSION=$(g++ -dumpversion | cut -d. -f1) + + if [ "$GPP_CURRENT_VERSION" -lt "$GPP_MINIMUM_VERSION" ]; then + TARGET_GPP="g++-${GPP_MINIMUM_VERSION}" + + if command -v "$TARGET_GPP" >/dev/null 2>&1; then + echo "Current g++ version ($GPP_CURRENT_VERSION) is less than $GPP_MINIMUM_VERSION. Switching to $TARGET_GPP." + + mkdir -p $HOME/.local/bin + ln -sf $(which "$TARGET_GPP") $HOME/.local/bin/g++ + + echo "$HOME/.local/bin" >> $GITHUB_PATH + else + echo "Warning: Current g++ ($GPP_CURRENT_VERSION) is less than $GPP_MINIMUM_VERSION, but $TARGET_GPP was not found in the system." + fi + else + echo "Current g++ version ($GPP_CURRENT_VERSION) is already $GPP_MINIMUM_VERSION or higher. Keeping default." + fi + - name: Wait for prerequisite steps wait-all: diff --git a/src/impls/cpp/README.md b/src/impls/cpp/README.md index 35c1b38..eab6fd6 100644 --- a/src/impls/cpp/README.md +++ b/src/impls/cpp/README.md @@ -3,7 +3,7 @@ # 前提条件・依存関係 C++コンパイラが必要です。 -`Makefile`を使用してビルド・実行する場合は、`g++`が必要です。 +`Makefile`を使用してビルド・実行する場合は、`g++`(14以降)が必要です。 # ビルドおよび実行 ## Makefileを使用する場合