C++23에서는 std::optional 타입에 모나딕(Monadic) 연산들을 추가하여 함수형 스타일의 체이닝(chaining)과 에러 처리를 더욱 편리하게 만들어주는 새로운 기능들이 도입되었습니다. 이번 글에서는 std::optional의 모나딕 연산(transform, transform_or, and_then, or_else)과 같은 기능의 개념과 사용법, 그리고 이전 버전과 비교하여 어떻게 개선되었는지 알아보겠습니다.
std::optional의 모나딕 연산이란 무엇인가요?
C++17에서 도입된 std::optional은 값이 존재할 수도 있고 존재하지 않을 수도 있는 상황을 안전하고 명확하게 처리하기 위한 타입입니다. 하지만 C++17에서는 std::optional에 대한 체이닝이나 함수 적용이 불편했으며, 값이 없는 경우를 처리하기 위해 조건문이나 별도의 분기 로직을 자주 사용해야 했습니다.
C++23에서는 std::optional에 다양한 모나딕 연산(transform, transform_or, and_then, or_else)을 추가하여, 함수형 프로그래밍 스타일로 값을 처리할 수 있게 되었습니다. 이를 통해 조건부 값에 대한 처리 로직을 간결하고 직관적으로 표현할 수 있습니다.
이전 버전에서는 어떻게 했나요?
C++17/20에서는 std::optional의 값을 변환하거나 다른 로직과 결합하기 위해 다음과 같은 패턴을 사용했습니다.
예제: 기존 방식 (C++17/20)
#include <optional>
#include <iostream>
#include <string>
std::optional<int> parseNumber(const std::string& s) {
try {
return std::stoi(s);
} catch (...) {
return std::nullopt;
}
}
int main() {
std::string input = "42";
std::optional<int> number = parseNumber(input);
// 값이 있는지 확인하고, 있으면 변환, 없으면 기본값 사용
int result;
if (number.has_value()) {
result = number.value() * 2;
} else {
result = -1; // 기본값
}
std::cout << result << '\n'; // 84 출력
return 0;
}
- 문제점: 값 변환 시마다 if (has_value()) 체크, 값이 없을 때의 처리(기본값 등)를 위한 별도 로직 필요
- 모나딕 스타일로 간결하게 표현하기 어렵고, 로직 중복 및 가독성 저하
C++23의 모나딕 연산을 사용한 개선
주요 모나딕 연산
- transform: 값이 존재하면 함수를 적용하여 변환, 값이 없으면 그대로 std::nullopt
- transform_or: 값이 존재하면 함수를 적용, 값이 없으면 지정한 대체값 반환
- and_then: 값이 존재하면 함수를 호출하고 그 결과(std::optional<T>) 반환, 없으면 std::nullopt
- or_else: 값이 없으면 함수를 호출해 대체 std::optional<T>를 반환, 있으면 원래 값 유지
예제: transform와 transform_or 사용
#include <optional>
#include <iostream>
#include <string>
std::optional<int> parseNumber(const std::string& s) {
try {
return std::stoi(s);
} catch (...) {
return std::nullopt;
}
}
int main() {
std::string input = "42";
std::optional<int> number = parseNumber(input);
// transform: 값이 있으면 2배로 변환, 없으면 nullopt 유지
auto doubled = number.transform([](int x) { return x * 2; });
// transform_or: 값이 있으면 2배, 없으면 -1 사용
int result = number.transform_or([](int x) { return x * 2; }, -1);
std::cout << (doubled.has_value() ? std::to_string(*doubled) : "nullopt") << '\n'; // 84
std::cout << result << '\n'; // 84 (값 있었으므로 2배)
return 0;
}
예제: and_then와 or_else 사용
#include <optional>
#include <iostream>
#include <string>
std::optional<int> parseNumber(const std::string& s) {
try {
return std::stoi(s);
} catch (...) {
return std::nullopt;
}
}
std::optional<int> half(int x) {
return (x % 2 == 0) ? std::optional<int>{x / 2} : std::nullopt;
}
int main() {
std::string input = "42";
std::optional<int> number = parseNumber(input);
// and_then: 값이 있으면 half 함수로 이어서 처리, 없으면 nullopt
auto halved = number.and_then(half);
// halved: 21 (42의 절반)
// or_else: 값이 없으면 다른 함수로 대체, 있으면 그대로
std::optional<int> fallback = halved.or_else([]() { return std::optional<int>(0); });
// halved에 값이 있으므로 fallback은 halved와 동일: 21
std::cout << *fallback << '\n'; // 21
return 0;
}
어떻게 좋아졌나요?
- 코드 가독성 향상: if/else 로직 없이도 값 존재 여부에 따른 변환 및 대체 로직을 간결하게 표현
- 유연성 증가: 체인 형태로 연속된 변환 및 대체 로직을 함수형 스타일로 구성 가능
- 중복 로직 감소: 값이 존재할 때/없을 때 로직을 명확하고 일관되게 처리할 수 있어 유지보수성 향상
- 표준화된 모나딕 스타일: 기존에 수동으로 구현하던 패턴을 표준 라이브러리에서 제공
상세한 예제와 비교
기존 방식 vs 모나딕 방식
기존 방식(C++20 이전)
auto parsed = parseNumber(input);
int result;
if (parsed.has_value()) {
auto halfVal = half(parsed.value());
if (halfVal.has_value()) {
result = halfVal.value();
} else {
result = 0;
}
} else {
result = -1;
}
C++23 방식(모나딕 연산)
int result = parseNumber(input)
.and_then(half)
.or_else([]() { return std::optional<int>(0); })
.transform_or([](int x){ return x; }, -1);
- 한 줄에 모든 로직을 담아 직관적으로 표현 가능
주의 사항
- 함수 인자의 형태: transform, and_then, or_else에 전달하는 람다는 적절한 타입 시그니처를 가져야 합니다(예: and_then은 std::optional<T>를 반환하는 함수).
- 성능 고려: 함수형 스타일로 체이닝하는 것이 가독성은 높이지만, 성능상 오버헤드가 있을 수 있으므로, 성능 민감한 부분에서는 신중한 판단 필요
- 컴파일러 지원: C++23 기능이므로, 해당 기능을 지원하는 컴파일러 및 표준 라이브러리가 필요
요약
C++23의 std::optional 모나딕 연산(transform, transform_or, and_then, or_else)을 통해 조건부 값 처리 로직을 함수형 스타일로 간결하게 표현할 수 있게 되었습니다. 이전에는 if/else와 value 체크가 난무하던 코드를 단순한 체이닝 형태로 재구성하여 가독성과 유지보수성을 높일 수 있으며, 표준화된 방식으로 모나딕 패턴을 지원하는 점에서 개발 생산성이 크게 향상됩니다.
참고 자료:
'개발 이야기 > C++' 카테고리의 다른 글
[C++23 새기능 소개] std::basic_string::resize_and_overwrite() (0) | 2024.12.09 |
---|---|
[C++23 새기능 소개] std::chrono 라이브러리 확장 (0) | 2024.12.08 |
[C++23 새기능 소개] std::unreachable() 함수 (0) | 2024.12.08 |
[C++23 새기능 소개] std::ranges::to 함수 템플릿 (0) | 2024.12.08 |
[C++23 새기능 소개] [[assume]] 속성 (0) | 2024.12.08 |