variant_visit2

variant_visit2 (std::visit 함수)

이 페이지는 C++ 표준 라이브러리의 std::visit 함수에 대해 설명해요. std::visitstd::variant 객체가 현재 보관 중인 값에 대해 주어진 방문자(visitor)를 호출하는 함수예요. 여러 variant를 동시에 방문할 수도 있고, C++20부터는 반환 타입을 명시할 수도 있어요.

출처: cppreference

본문

함수 선언

<variant> 헤더에 정의되어 있어요.

템플릿 선언 (1) (since C++17)
template < class Visitor , class ... Variants > constexpr /* see below */ visit ( Visitor && v , Variants && ... values ); (1) (since C++17)
template < class R , class Visitor , class ... Variants > constexpr R visit ( Visitor && v , Variants && ... values ); (2) (since C++20)

헬퍼 템플릿:

템플릿 선언 (3) (설명 전용*)
template < class ... Ts > auto && /*as-variant*/ ( std :: variant < Ts ... >& value ); (3) (설명 전용*)
template < class ... Ts > auto && /*as-variant*/ ( const std :: variant < Ts ... >& value ); (4) (설명 전용*)
template < class ... Ts > auto && /*as-variant*/ ( std :: variant < Ts ... >&& value ); (5) (설명 전용*)
template < class ... Ts > auto && /*as-variant*/ ( const std :: variant < Ts ... >&& value ); (6) (설명 전용*)

방문자 v( Variants의 타입들로부터 만들 수 있는 모든 조합으로 호출 가능한 Callable)를 Variants 값들에 적용해요.

VariantBasesdecltype(as-variant(std::forward<Variants>(values)))... (즉 sizeof...(Variants)개의 타입으로 이루어진 팩)일 때, 다음 식이 수행돼요.

INVOKE(std::forward<Visitor>(v), std::get<indices>(std::forward<VariantBases>(values))...),

그리고 C++20부터는:

INVOKE<R>(std::forward<Visitor>(v), std::get<indices>(std::forward<VariantBases>(values))...).

이 오버로드들은 VariantBases의 모든 타입이 유효한 타입일 때만 오버로드 해석에 참여해요. INVOKE 또는 INVOKE<R>(C++20부터)로 표시된 식이 유효하지 않거나, 서로 다른 indices에 대해 INVOKE 또는 INVOKE<R>(C++20부터)의 결과 타입이나 값 범주가 다르면 프로그램은 ill-formed예요.

매개변수

v - Variants의 모든 variant에서 가능한 모든 대안을 받아들일 수 있는 Callable
values - 방문자에게 전달할 variant 목록

반환값

반환값은 방문자 호출 결과로 결정돼요.

예외

values에 있는 어떤 variant value_i에 대해 as-variant(value_i).valueless_by_exception()true이면 std::bad_variant_access를 던져요.

복잡도

variant의 개수가 0 또는 1이면 호출 가능 객체의 호출은 상수 시간에 구현돼요. 즉, variant에 저장될 수 있는 타입의 수에 의존하지 않아요. variant의 개수가 1보다 많으면 호출 가능 객체의 호출에 대한 복잡도 요구 사항은 없어요.

참고 사항

n(1 * ... * std::variant_size_v<std::remove_reference_t<VariantBases>>)라고 할 때, 구현은 보통 std::visit의 각 특수화에 대해 n개의 함수 포인터로 이루어진 (아마 다차원) 배열과 동등한 테이블을 생성해요. 이는 가상 함수의 구현과 비슷해요.

구현은 std::visit에 대해 n개의 분기를 가진 switch 문을 생성할 수도 있어요. (예를 들어 MSVC STL 구현은 n이 256보다 크지 않을 때 switch 문을 사용해요.)

일반적인 구현에서 v 호출의 시간 복잡도는 (아마 다차원) 배열의 요소에 접근하거나 switch 문을 실행하는 것과 같다고 볼 수 있어요.

기능 테스트 매크로 표준 기능
__cpp_lib_variant 202102L (C++23) (DR17) std::variant에서 파생된 클래스에 대한 std::visit

예제

#include <iomanip>
#include <iostream>
#include <string>
#include <type_traits>
#include <variant>
#include <vector>

// the variant to visit
using value_t = std::variant<int, long, double, std::string>;

// helper type for the visitor #4
template<class... Ts>
struct overloaded : Ts... { using Ts::operator()...; };
// explicit deduction guide (not needed as of C++20)
template<class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;

int main()
{
    std::vector<value_t> vec = {10, 15l, 1.5, "hello"};

    for (auto& v: vec)
    {
        // 1. void visitor, only called for side-effects (here, for I/O)
        std::visit([](auto&& arg){ std::cout << arg; }, v);

        // 2. value-returning visitor, demonstrates the idiom of returning another variant
        value_t w = std::visit([](auto&& arg) -> value_t { return arg + arg; }, v);

        // 3. type-matching visitor: a lambda that handles each type differently
        std::cout << ". After doubling, variant holds ";
        std::visit([](auto&& arg)
        {
            using T = std::decay_t<decltype(arg)>;
            if constexpr (std::is_same_v<T, int>)
                std::cout << "int with value " << arg << '\n';
            else if constexpr (std::is_same_v<T, long>)
                std::cout << "long with value " << arg << '\n';
            else if constexpr (std::is_same_v<T, double>)
                std::cout << "double with value " << arg << '\n';
            else if constexpr (std::is_same_v<T, std::string>)
                std::cout << "std::string with value " << std::quoted(arg) << '\n';
            else
                static_assert(false, "non-exhaustive visitor!");
        }, w);
    }

    for (auto& v: vec)
    {
        // 4. another type-matching visitor: a class with 3 overloaded operator()'s
        // Note: The `(auto arg)` template operator() will bind to `int` and `long`
        //       in this case, but in its absence the `(double arg)` operator()
        //       *will also* bind to `int` and `long` because both are implicitly
        //       convertible to double. When using this form, care has to be taken
        //       that implicit conversions are handled correctly.
        std::visit(overloaded{
            [](auto arg) { std::cout << arg << ' '; },
            [](double arg) { std::cout << std::fixed << arg << ' '; },
            [](const std::string& arg) { std::cout << std::quoted(arg) << ' '; }
        }, v);
    }
}

출력:

10. After doubling, variant holds int with value 20
15. After doubling, variant holds long with value 30
1.5. After doubling, variant holds double with value 3
hello. After doubling, variant holds std::string with value "hellohello"
10 15 1.500000 "hello"

결함 보고서

다음 동작 변경 결함 보고서는 이전에 발표된 C++ 표준에 소급 적용되었어요.

DR 적용 대상 발표된 동작 올바른 동작
LWG 2970 C++17 오버로드 (1)의 반환 타입이 INVOKE 연산 결과의 값 범주를 보존하지 않았음 보존함
LWG 3052 ( P2162R2 ) C++17 Variants의 어떤 타입이 std::variant가 아니면 효과가 불명확했음 명확히 지정됨

같이 보기

visit (C++26) variant가 보관한 인자로 주어진 함수 객체를 호출해요 (공개 멤버 함수) [edit]
swap 다른 variant와 교환해요 (공개 멤버 함수) [edit]

더 알아보기 (Learn more)

cppreference