반응형

Modern C++에서 다중 타입 반환(multi-type return) 방법 정리

  • Modern C++에서는 하나의 함수(function)에서 반환값(return value)의 타입(type)을 선택적으로 반환하는 다양한 방법이 있습니다.
  • 이 글에서는 C++11부터 C++23까지 사용할 수 있는 여섯 가지 방법 을 소개하고, 각각의 장단점을 비교해 보겠습니다.


1. std::variant (C++17)

  • C++17에서 도입된 std::variant하나의 변수에 여러 타입을 저장할 수 있는 방법입니다.

  • cpp

      #include <variant>
      #include <iostream>
      #include <string>
      
      std::variant<int, std::string> to_int(const std::string& text) {
          try {
              return std::stoi(text);
          } catch (...) {
              return "Invalid number: " + text;
          }
      }
      
      int main() {
          auto result = to_int("42");
          if (std::holds_alternative<int>(result)) {
              std::cout << "Integer: " << std::get<int>(result) << "\n";
          } else {
              std::cout << "Error: " << std::get<std::string>(result) << "\n";
          }
      }
    

  • 장점: 다양한 타입을 하나의 컨테이너로 묶을 수 있음.
  • 단점: std::get<> 또는 std::visit을 사용해야 함.


2. std::optional (C++17)

  • C++17std::optional을 사용하면 값이 존재하는지 여부를 쉽게 표현 할 수 있습니다.

  • cpp

      #include <optional>
      #include <iostream>
      #include <string>
      
      std::optional<int> to_int(const std::string& text) {
          try {
              return std::stoi(text);
          } catch (...) {
              return std::nullopt;
          }
      }
      
      int main() {
          auto result = to_int("42");
          if (result) {
              std::cout << "Integer: " << *result << "\n";
          } else {
              std::cout << "Error: Invalid number\n";
          }
      }
    

  • 장점: 값이 존재하는지 여부만 판단하면 됨.
  • 단점: 오류 메시지를 전달할 수 없음.


3. std::expected (C++23)

  • C++23에서 도입된 std::expected성공값과 오류 메시지를 함께 표현 할 수 있는 공식적인 방법입니다.

  • cpp

      #include <expected>
      #include <iostream>
      #include <string>
      
      std::expected<int, std::string> to_int(const std::string& text) {
          try {
              return std::stoi(text);
          } catch (...) {
              return std::unexpected("Invalid number: " + text);
          }
      }
      
      int main() {
          auto result = to_int("42");
          if (result) {
              std::cout << "Integer: " << *result << "\n";
          } else {
              std::cout << "Error: " << result.error() << "\n";
          }
      }
    

  • 장점: 성공값과 실패값을 명확하게 구분 가능.
  • 단점: C++23 이상에서만 사용 가능.
    • Compiler std::expected 지원 버전 비고
      GCC 13.1 이상 GCC Toolset 13부터 지원됩니다.
      Clang 16.0 이상 Clang 16부터 C++23 기능을 지원하기 시작했습니다.
      MSVC Visual Studio 2022 버전 17.6 (컴파일러 버전 19.36) 이상 C++23 기능이 포함되었습니다.


4. std::pair 활용 (C++11)

  • std::pair<int, std::string>을 사용하면 첫 번째 요소에 변환된 값, 두 번째 요소에 오류 메시지를 저장 할 수 있습니다.

  • cpp

      #include <iostream>
      #include <string>
      #include <utility>
      
      std::pair<int, std::string> to_int(const std::string& text) {
          try {
              return { std::stoi(text), "" };
          } catch (...) {
              return { 0, "Invalid number: " + text };
          }
      }
      
      int main() {
          auto [value, error] = to_int("hello");
          if (error.empty()) {
              std::cout << "Integer: " << value << "\n";
          } else {
              std::cout << "Error: " << error << "\n";
          }
      }
    

  • 장점: 간단한 구조.
  • 단점: intstd::string이 동시에 존재하므로 다소 비효율적.


5. struct + std::optional 활용 (C++11)

  • 구조체를 사용하면 명확한 의미를 갖는 데이터를 반환 할 수 있습니다.

  • cpp

      #include <iostream>
      #include <string>
      #include <optional>
      
      struct Result {
          std::optional<int> value;
          std::string error;
      };
      
      Result to_int(const std::string& text) {
          try {
              return { std::stoi(text), "" };
          } catch (...) {
              return { std::nullopt, "Invalid number: " + text };
          }
      }
      
      int main() {
          auto result = to_int("42");
          if (result.value) {
              std::cout << "Integer: " << *result.value << "\n";
          } else {
              std::cout << "Error: " << result.error << "\n";
          }
      }
    

  • 장점: 가독성이 뛰어남.
  • 단점: 구조체 정의가 필요함.


6. std::any 활용 (C++17)

  • std::any어떤 타입이든 반환할 수 있는 유연한 방법이지만, 런타임 타입 체크가 필요 합니다.

  • cpp

      #include <any>
      #include <iostream>
      #include <string>
      
      std::any to_int(const std::string& text) {
          try {
              return std::stoi(text);
          } catch (...) {
              return "Invalid number: " + text;
          }
      }
      
      int main() {
          auto result = to_int("42");
          if (result.type() == typeid(int)) {
              std::cout << "Integer: " << std::any_cast<int>(result) << "\n";
          } else {
              std::cout << "Error: " << std::any_cast<std::string>(result) << "\n";
          }
      }
    

  • 장점: 모든 타입을 지원
  • 단점: 런타임 타입 체크 필요 (typeidstd::any_cast<> 사용)


🔍 정리

방법 C++ 버전 장점 단점
std::variant<int, std::string> C++17 다양한 타입 반환 가능 std::get<> 사용 필요
std::optional<int> C++17 단순한 성공/실패 표현 오류 메시지 전달 불가
std::expected<int, std::string> C++23 명확한 성공/실패 처리 C++23 이상에서만 사용 가능
std::pair<int, std::string> C++11 쉬운 구현 intstd::string이 항상 존재
struct + std::optional C++11 명확한 의미 구조체 정의 필요
std::any C++17 모든 타입을 지원 런타임 타입 체크 필요
  • ✅ 추천 방법
    • C++17 이상: std::variant<int, std::string>
    • C++23 이상: std::expected<int, std::string>
    • 🚀 만약 오류 메시지가 필요 없다면?std::optional<int>
    • 🔧 명확한 구조가 필요하다면?struct + std::optional
728x90
반응형

+ Recent posts