핵심 정리
cpp
#include <variant>
#include <string>
std::variant<int, double, std::string> val = 42;
val = std::string("hello"); // 다른 타입으로 교체
std::visit([](const auto& v) {
std::cout << v << "\n";
}, val); // 현재 담긴 타입에 맞는 분기 자동 선택문법
union vs variant — 타입 안전성 차이
구식 union은 어떤 타입이 활성화되어 있는지 추적하지 않아 잘못된 타입으로 접근하면 UB가 발생합니다. variant는 현재 활성 타입을 내부적으로 추적하여 잘못된 접근 시 예외를 던집니다.
cpp
// ❌ 구식 union — 타입 추적 없음, UB 위험
union Old { int i; double d; };
Old u; u.d = 3.14;
std::cout << u.i; // ❌ UB — 활성화된 멤버 아님
// ✅ variant — 활성 타입 추적, 잘못된 접근 시 예외
std::variant<int, double> v = 3.14;
std::get<int>(v); // ❌ std::bad_variant_access 예외
std::get<double>(v); // ✅ 3.14get과 get_if — 접근 방식 선택
cpp
std::variant<int, std::string> v = std::string("hello");
// get<T> — 타입 불일치 시 예외
std::string s = std::get<std::string>(v); // ✅
int n = std::get<int>(v); // ❌ std::bad_variant_access
// get_if<T> — 타입 불일치 시 nullptr (예외 없음)
if (auto* p = std::get_if<std::string>(&v)) {
std::cout << *p; // ✅ 존재할 때만 접근
}
// index() — 활성 타입의 인덱스 확인
std::cout << v.index(); // 1 (std::string은 두 번째 타입)std::visit — 모든 타입을 처리하는 방문자 패턴
visit는 variant에 담긴 타입에 따라 적절한 함수 오버로드나 람다를 자동으로 선택합니다. 새 타입이 추가되면 컴파일러가 처리 누락을 경고합니다.
cpp
std::variant<int, double, std::string> v = 42;
// 제네릭 람다 — 모든 타입 동일 처리
std::visit([](const auto& x) {
std::cout << x << "\n";
}, v);
// 오버로드 패턴 — 타입별 다른 처리 (C++17)
struct Visitor {
void operator()(int i) { std::cout << "int: " << i << "\n"; }
void operator()(double d) { std::cout << "double: " << d << "\n"; }
void operator()(const std::string& s) { std::cout << "str: " << s << "\n"; }
};
std::visit(Visitor{}, v);variant vs 상속 계층 — 선택 기준
cpp
// variant가 적합 — 닫힌 타입 집합, 값 의미
using Token = std::variant<int, double, std::string, bool>;
// 새 토큰 타입은 variant에 추가 → visit 처리가 바뀜
// 상속이 적합 — 열린 확장, 동적 다형성
class Shape { virtual double area() = 0; };
class Circle : public Shape { ... };
class Square : public Shape { ... };
// 새 도형 추가 시 기존 코드 변경 없음체크포인트
| 상황 | 적합한 선택 |
|---|---|
| 닫힌 타입 집합 중 하나 | std::variant<A, B, C> |
| 현재 타입에 따른 분기 | std::visit |
| 안전한 타입 접근 (예외 원할 때) | std::get<T>(v) |
| 안전한 타입 접근 (nullptr 선호) | std::get_if<T>(&v) |
| 새 타입 추가가 자주 필요한 열린 구조 | 상속 계층 |
주의할 점
std::get<T>는 활성 타입이 다르면 예외가 발생합니다. 타입을 확신하지 못하면 get_if를 사용하세요.
cpp
std::variant<int, std::string> v = 42;
// ❌ 타입 확인 없이 get — 예외 위험
std::string s = std::get<std::string>(v); // std::bad_variant_access!
// ✅ get_if로 안전하게 접근
if (auto* p = std::get_if<std::string>(&v)) {
std::cout << *p;
} else {
std::cout << "string 아님\n";
}
// ✅ visit으로 모든 경우 처리
std::visit([](auto& x) { std::cout << x; }, v);
// ⚠️ variant는 기본 생성 시 첫 번째 타입으로 초기화
std::variant<int, std::string> def; // int{0}으로 초기화참고 링크
2 sources