C++ std::variant, 여러 타입 중 하나를 담는 법
std::variant는 지정한 타입 목록 중 하나를 한 객체에 담는 C++17 유틸리티이며, 태그 있는 유니온이 필요할 때 씁니다.
이 글은 cppreference의 variant 문서를 2026-09-01 기준으로 정리한 일반 설명이며, 표준 버전과 구현에 따라 세부 동작은 달라질 수 있습니다.
std::variant는 무엇인가?
한 줄 답: 이 타입은 지정한 타입 목록 가운데 하나의 값을 보관하는 타입 안전한 유니온이며, C++17부터 <variant> 헤더에서 제공합니다.
이 타입은 template< class... Types > class variant; 형태로 선언하는 클래스 템플릿입니다. 한 시점에는 지정한 대안 중 하나의 값을 보관하거나, 예외로 값 교체가 끝나지 않은 경우 값 없음 상태가 될 수 있습니다. 보관된 객체는 유니온과 마찬가지로 이 객체 안에 중첩됩니다.
대안으로 참조, 배열, void는 지정할 수 없습니다. 같은 타입을 대안 목록에 여러 번 넣는 것은 가능하지만, 타입으로 접근할 때에는 해당 타입이 정확히 한 번만 나타나야 합니다. 따라서 중복 타입을 사용한다면 인덱스 기반 접근처럼 대안을 구별할 방법이 필요합니다.
기본 생성한 객체는 첫 번째 대안을 보관합니다. 첫 번째 대안이 기본 생성 불가능하면 이 객체도 기본 생성할 수 없으므로, 기본 상태를 표현할 대안이 필요할 때 std::monostate를 첫 번째 대안으로 둘 수 있습니다. 템플릿 인수 없이 선언하는 것은 올바르지 않으며, 기본 상태만 필요하면 std::variant<std::monostate>처럼 작성할 수 있습니다.
union과 무엇이 다른가?
한 줄 답: union은 활성 멤버를 작성자가 따로 추적해야 할 수 있지만, 이 타입은 현재 대안을 추적해 확인과 접근을 위한 표준 인터페이스를 제공합니다.
union은 한 번에 비정적 데이터 멤버 하나만 보관할 수 있습니다. 가장 최근에 기록하지 않은 멤버를 읽으면 정의되지 않은 동작이므로, 어떤 멤버가 활성 상태인지 사용하는 코드가 별도로 관리해야 합니다.
태그 있는 유니온에서는 작성자가 태그와 실제 저장 멤버가 일치하도록 유지합니다. 이 객체는 현재 대안의 위치를 index()로 확인하고, 특정 타입이 활성 상태인지 holds_alternative로 확인할 수 있습니다. 저장된 대안과 맞지 않는 타입이나 인덱스로 std::get을 호출하면 정의되지 않은 동작 대신 std::bad_variant_access가 발생합니다.
값은 어떻게 넣고 꺼내나?
한 줄 답: 대입 또는 emplace로 대안을 넣고, 현재 대안을 확인한 뒤 std::get 또는 get_if로 값을 읽습니다.
대입은 변환이 모호하지 않을 때 해당 대안을 보관하게 하며, emplace는 선택한 대안을 객체 안에 직접 생성합니다. 기본 생성 직후에는 첫 번째 대안이 보관되므로, 대입으로 다른 대안을 선택하거나 emplace로 원하는 대안을 생성할 수 있습니다.
std::get<T>는 타입으로, std::get<I>는 0부터 시작하는 인덱스로 저장값에 대한 참조를 얻습니다. 타입 기반 형식은 T가 대안 목록에 정확히 한 번 있어야 하고, 인덱스 기반 형식은 유효한 대안 위치여야 합니다. 현재 대안과 요청한 타입 또는 인덱스가 다르면 std::get은 std::bad_variant_access를 던집니다.
접근 전에 holds_alternative<T>로 현재 타입을 확인할 수 있습니다. 예외를 사용하는 대신 std::get_if<T>(&value)를 호출하면 대안이 맞을 때 저장값을 가리키는 포인터를 받고, 맞지 않거나 전달한 주소가 널 포인터이면 널 포인터를 받습니다.
std::variant<int, float> value;
value = 42;
if (std::holds_alternative<int>(value)) {
int number = std::get<int>(value);
}
if (const int* number = std::get_if<int>(&value)) {
// *number를 사용합니다.
}
index()는 현재 대안의 0부터 시작하는 위치를 반환합니다. 예외 중 값 교체가 실패해 값을 보관하지 못한 드문 상태에서는 valueless_by_exception()이 참이고, index()는 variant_npos를 반환하며 std::get은 std::bad_variant_access를 던집니다.
visit은 언제 쓰나?
한 줄 답: 현재 대안마다 같은 분기 지점을 반복하지 않고, 보관된 타입에 따라 처리를 나눠야 할 때 C++17 자유 함수 std::visit을 사용합니다.
std::visit은 방문자와 하나 이상의 객체를 받아 현재 보관된 대안을 인자로 방문자를 호출합니다. 방문자는 전달될 수 있는 각 대안 타입의 조합에 대해 호출할 수 있어야 하므로, 한 객체를 처리할 때는 일반 람다처럼 작성할 수 있습니다.
std::variant<int, float> object = 42;
std::visit([](auto&& value) {
// value의 실제 타입에 맞게 처리합니다.
}, object);
대안별로 다른 동작이 필요하면 각 호출 연산자를 가진 오버로드 집합을 방문자로 넘길 수 있습니다. 방문 대상 중 하나라도 valueless_by_exception 상태이면 std::bad_variant_access가 발생합니다. 방문 대상이 하나 이하일 때 호출 복잡도는 대안 수에 의존하지 않는 상수 시간이라고 설명됩니다.
멤버 visit은 C++26에 추가되며 자유 함수 호출과 동등한 형태를 제공합니다. 이 글의 주 경로는 C++17 자유 함수 std::visit입니다.
FAQ
한 줄 답: 대안의 확인, 잘못된 접근, 선택적 값과의 구분, 예외 상태를 알면 이 객체를 필요한 범위에서 사용할 수 있습니다.
Q. 여러 타입 중 하나를 타입 안전하게 담으려면 무엇을 씁니까? A. 지정한 대안 중 하나와 현재 대안을 함께 다뤄야 할 때 C++17의 이 유틸리티를 사용합니다.
Q. 잘못된 대안으로 std::get을 호출하면 어떻게 됩니까? A. 저장된 대안과 요청한 타입 또는 인덱스가 맞지 않으면 std::bad_variant_access가 발생합니다. 먼저 holds_alternative로 확인하거나 get_if의 널 여부로 분기할 수 있습니다.
Q. optional과 이 타입은 언제 구분합니까? A. 값 또는 없음만 필요하면 optional을 사용하고, 여러 타입 가운데 하나를 보관해야 하면 이 타입을 사용합니다.
Q. valueless_by_exception은 언제입니까? A. 값 교체 중 예외가 발생해 값을 보관하지 못한 경우에 나타날 수 있는 드문 상태입니다. 이때 index()는 variant_npos를 반환합니다.
출처
한 줄 답: 이 글의 정의, 접근 규칙, 방문, 예외 상태의 근거는 cppreference의 <variant> 및 union 문서에서 확인합니다.
본문의 표준 동작과 코드 예시는 2026-09-01 기준으로 확인한 아래의 공식 cppreference 문서를 바탕으로 정리합니다.
- variant 개요 — C++17 선언, 타입 안전한 유니온, 기본 상태
std::get— 타입·인덱스 기반 접근과bad_variant_accessholds_alternative— 현재 대안 확인get_if— 포인터 기반의 예외 없는 접근index— 현재 대안의 인덱스valueless_by_exception— 값 없음 상태monostate— 기본 생성 가능한 첫 대안std::visit자유 함수 — C++17 방문visit멤버 함수 — C++26 멤버 방문union— 활성 멤버와 태그 있는 유니온<variant>헤더 — 헤더 선언- C++ 기술면접 질문 10가지 — C++ 개념을 점검하는 별도 글이며, 여러 타입을 하나의 객체에 담는 표준 유틸리티를 설명하는 이 글과 범위가 다릅니다.