안녕하세요. 이번 글에서는 C++17에서 추가된 std::variant에 대해 알아보겠습니다.
여러 타입 중 하나의 값만 담는 상황에서는 예전에는 union을 쓰거나 부모 클래스를 두고 상속을 이용했습니다. union은 현재 어떤 타입이 들어 있는지 직접 관리해야 하고 std::string 같은 클래스 타입을 다루기 까다롭습니다. variant는 타입 안전한 union으로, 현재 담긴 타입을 스스로 기억합니다.
기본 사용법
헤더는 <variant>이며 C++17 이상(-std=c++17)이 필요합니다. 템플릿 인자로 담을 수 있는 타입 목록을 나열하고, 값을 대입하면 해당 타입이 활성화됩니다. 기본 생성하면 첫 번째 타입의 기본값을 가집니다.
#include <iostream>
#include <string>
#include <variant>
using namespace std;
int main()
{
variant<int, double, string> v; // int 0 (첫 번째 타입)
cout << v.index() << "\n"; // 0
v = 3.14;
cout << v.index() << "\n"; // 1
v = string("hello");
cout << v.index() << "\n"; // 2
cout << get<string>(v) << "\n"; // hello
}
값 꺼내기 : get, get_if, holds_alternative
| 함수 | 설명 |
|---|---|
| index() | 현재 활성 타입의 번호 (0부터) |
| holds_alternative<T>(v) | 현재 T가 담겨 있는지 확인 |
| get<T>(v) / get<N>(v) | 값 반환. 다른 타입이면 std::bad_variant_access 예외 |
| get_if<T>(&v) | T가 담겨 있으면 포인터, 아니면 nullptr (예외 없음) |
variant<int, string> v = 42;
if (holds_alternative<int>(v))
cout << "int : " << get<int>(v) << "\n";
if (auto p = get_if<string>(&v))
cout << "string : " << *p << "\n";
else
cout << "string이 아닙니다\n";
try
{
cout << get<string>(v); // 활성 타입이 int이므로 예외
}
catch (const bad_variant_access& e)
{
cout << e.what() << "\n";
}
std::visit : 타입별로 처리하기
담긴 타입에 따라 다르게 동작해야 할 때는 std::visit을 씁니다. 모든 타입을 처리하는 호출 가능 객체를 넘기면 컴파일 타임에 빠진 타입이 없는지 검사됩니다. 람다 여러 개를 하나로 묶는 overloaded 도우미를 함께 쓰는 방식이 흔합니다.
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>; // C++17에서 필요
variant<int, double, string> v = string("variant");
visit(overloaded{
[](int i) { cout << "int : " << i << "\n"; },
[](double d) { cout << "double : " << d << "\n"; },
[](const string& s) { cout << "string : " << s << "\n"; }
}, v);
주의할 점
- 기본 생성이 불가능한 타입이 첫 번째에 오면 variant도 기본 생성할 수 없습니다. 이때는 첫 번째에 std::monostate를 두어 “값 없음” 상태를 표현합니다.
- 같은 타입을 두 번 나열하면 타입으로 접근할 수 없으므로 get<N> 처럼 인덱스로 접근해야 합니다.
- 대입 도중 예외가 발생하면 값이 없는 상태(valueless_by_exception)가 될 수 있습니다.
- 값이 있는지 없는지만 표현하려면 std::optional이 더 단순합니다.
참고
이상으로 std::variant에 대해 간단히 알아보았습니다.