/

|

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::getstd::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::getstd::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 문서를 바탕으로 정리합니다.

std::variant is a C++17 utility for holding one value selected from a declared list of types in a single object, which is useful when a tagged union fits the design.

This article is a general explanation of cppreference's variant documentation as checked on 2026-09-01. Detailed behavior can vary with the standard version and implementation.

What is std::variant?

Short answer: This C++17 class template from <variant> is a type-safe union that stores one selected value from its specified alternatives.

Its declaration form is template< class... Types > class variant;. At any given time, the object stores one of the specified alternatives, or it can become valueless when an exceptional replacement does not complete. As with a union, the stored object is nested inside the variant object.

References, arrays, and void cannot be alternatives. The same type may appear more than once, but type-based access requires that type to occur exactly once. When alternatives are duplicated, use a distinguishing route such as index-based access.

Default construction selects the first alternative. If that first type is not default-constructible, the variant cannot be default-constructed either. Put std::monostate first when an explicit default state is needed; std::variant<std::monostate> is valid when that is the only state required. A declaration with no template arguments is ill-formed.

How is it different from a union?

Short answer: A union can require the programmer to track the active member, whereas std::variant tracks its active alternative and provides standard interfaces for inspecting and accessing it.

A union holds one non-static data member at a time. Reading a member other than the most recently written one is undefined behavior, so the surrounding code has to manage which member is active.

With a tagged union, the programmer keeps a tag synchronized with the stored member. With std::variant, index() reports the current alternative and holds_alternative checks whether a particular type is active. If std::get is given a type or index that does not match the stored alternative, it throws std::bad_variant_access instead of producing undefined behavior.

How do you store and retrieve a value?

Short answer: Assignment selects an unambiguous convertible alternative, while emplace constructs a chosen alternative directly in the object; inspect the active alternative before reading it with std::get or get_if.

A just-default-constructed object holds its first alternative. Assignment can then select another unambiguous convertible alternative, while emplace constructs the chosen alternative directly inside the object.

std::get<T> obtains a reference by type, while std::get<I> uses a zero-based alternative index. For the type-based form, T must occur exactly once in the alternatives; for the index-based form, I must identify a valid alternative. If the requested type or index does not match the current alternative, std::get throws std::bad_variant_access.

Use holds_alternative<T> to check the active type before access. For a non-throwing path, std::get_if<T>(&value) returns a pointer to the stored value when the alternative matches and a null pointer otherwise, including when the supplied address is null.

The following compact example shows checked std::get and pointer-based get_if; it is not a complete program.

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() normally returns the zero-based position of the current alternative. In the rare no-value state where an exception prevents a replacement from completing, valueless_by_exception() is true, index() returns variant_npos, and std::get throws std::bad_variant_access.

When should you use visit?

Short answer: Use the C++17 free function std::visit when each active type needs different handling and repeating the same branch point for every alternative would be unnecessary.

std::visit takes a visitor and one or more variant objects, then invokes the visitor with the alternatives currently stored. The visitor must be callable for every possible combination of alternative types supplied by those objects. For one object, a generic lambda is the straightforward visitor shape.

std::variant<int, float> object = 42;

std::visit([](auto&& value) {
    // value의 실제 타입에 맞게 처리합니다.
}, object);

When alternatives need different actions, pass an overload set with the required call operators as the visitor. If any visited object is in the valueless_by_exception state, the operation throws std::bad_variant_access. For no more than one visited object, the documented call complexity does not depend on the number of alternatives; it is described as constant time.

Member visit was added in C++26 and has an equivalent form to the free-function call. This article's primary path remains the C++17 free function std::visit.

FAQ

Short answer: Use the type after confirming the active alternative, understanding mismatched access, separating a choice among types from a merely optional value, and recognizing the exceptional no-value state.

QuestionAnswer
What should I use to type-safely store one of several types? Use std::variant, this C++17 utility, when an object must hold one declared alternative and its current alternative must be handled with it.
What happens if std::get requests the wrong alternative? A type or index that does not match the stored alternative causes std::bad_variant_access. Check with holds_alternative first or branch on whether get_if returns null.
When should I choose optional instead? Use optional when the model is a value or no value; use std::variant when the model is one of several types.
When can valueless_by_exception be true? It is a rare state that can occur when replacing the value throws and no value remains; index() then returns variant_npos.

Sources

Short answer: The definitions, access rules, visiting behavior, and exception-state facts in this article are checked against cppreference's <variant> and union documentation as of 2026-09-01.

The standard behavior and code examples below were compiled from the official cppreference documentation listed here and checked on that date.

+ Recent posts