C++
std :: variant
수색…
비고
Variant는 원시 union
사용을 대체합니다. 그것은 유형 안전하고 어떤 유형인지 알고 있으며, 조심스럽게 그것을 구성하고 그 안에있는 객체를 파괴해야합니다.
그것은 거의 결코 비어 있지 않습니다. 내용을 대체하는 것이 구석에서 발생하고 안전하게 되돌릴 수없는 경우에만 비어있는 상태가됩니다.
이것은 std::tuple
과 다소 유사하고 std::optional
과 다소 유사합니다.
std::get
과 std::get_if
를 사용하는 것은 나쁜 생각입니다. 정답은 일반적으로 std::visit
입니다. 바로 std::visit
하여 모든 가능성을 처리 할 수 있습니다. if constexpr
을 visit
내에서 사용할 수 있다면 어떤 visit
이 더 효율적으로 반복되는지 런타임 검사 시퀀스를 수행하는 것이 아니라 동작을 분기해야합니다.
기본 표준 :: 변형 사용
이렇게하면 int
또는 string
저장할 수있는 변형 (태그가 지정된 공용체)이 만들어집니다.
std::variant< int, std::string > var;
우리는 그 중 하나를 저장할 수 있습니다 :
var = "hello"s;
그리고 우리는 std::visit
통해 내용에 접근 할 수 있습니다 :
// Prints "hello\n":
visit( [](auto&& e) {
std::cout << e << '\n';
}, var );
다형성 람다 또는 유사한 함수 객체를 전달합니다.
우리가 어떤 유형인지 알면 확신 할 수 있습니다.
auto str = std::get<std::string>(var);
그러나 우리가 틀리게되면 이것은 버릴 것입니다. get_if
:
auto* str = std::get_if<std::string>(&var);
잘못 추측 한 경우 nullptr
반환합니다.
변형은 동적 메모리 할당을 보장하지 않습니다 (포함 된 유형에 의해 할당 된 것 외에는). 변형에있는 유형 중 하나만이 거기에 저장되며 드문 경우 (배정하는 동안 예외를 포함하고 철회 할 안전한 방법이 없음) 변형이 비어있을 수 있습니다.
변형을 사용하면 하나의 변수에 여러 값 유형을 안전하고 효율적으로 저장할 수 있습니다. 그것들은 기본적으로 똑똑하고 형체가 안전한 union
입니다.
의사 메소드 포인터 만들기
이것은 고급 예입니다.
경량 유형 삭제를 위해 변형을 사용할 수 있습니다.
template<class F>
struct pseudo_method {
F f;
// enable C++17 class type deduction:
pseudo_method( F&& fin ):f(std::move(fin)) {}
// Koenig lookup operator->*, as this is a pseudo-method it is appropriate:
template<class Variant> // maybe add SFINAE test that LHS is actually a variant.
friend decltype(auto) operator->*( Variant&& var, pseudo_method const& method ) {
// var->*method returns a lambda that perfect forwards a function call,
// behaving like a method pointer basically:
return [&](auto&&...args)->decltype(auto) {
// use visit to get the type of the variant:
return std::visit(
[&](auto&& self)->decltype(auto) {
// decltype(x)(x) is perfect forwarding in a lambda:
return method.f( decltype(self)(self), decltype(args)(args)... );
},
std::forward<Var>(var)
);
};
}
};
이 operator->*
는 왼쪽에 Variant
가있는 operator->*
를 오버로드하는 형식을 만듭니다.
// C++17 class type deduction to find template argument of `print` here.
// a pseudo-method lambda should take `self` as its first argument, then
// the rest of the arguments afterwards, and invoke the action:
pseudo_method print = [](auto&& self, auto&&...args)->decltype(auto) {
return decltype(self)(self).print( decltype(args)(args)... );
};
이제 print
메소드를 가진 각각 2 가지 타입이 있다면 :
struct A {
void print( std::ostream& os ) const {
os << "A";
}
};
struct B {
void print( std::ostream& os ) const {
os << "B";
}
};
그것들은 관련이없는 유형이라는 것을주의하십시오. 우리는 할 수있다 :
std::variant<A,B> var = A{};
(var->*print)(std::cout);
그리고 우리에게 직접 호출을 A::print(std::cout)
에 A::print(std::cout)
것이다. 우리가 B{}
var
를 초기화한다면 B::print(std::cout)
디스패치 할 것이다.
새로운 유형 C를 만든 경우 :
struct C {};
그때:
std::variant<A,B,C> var = A{};
(var->*print)(std::cout);
C.print(std::cout)
메소드가 없으므로 컴파일에 실패합니다.
위의 내용을 확장하면 free function print
가 감지되어 사용 될 수 있으며 아마도 print
pseudo-method 내에서 if constexpr
을 사용 if constexpr
가능합니다.
std::variant
대신에 boost::variant
를 사용하는 예제 입니다.
`std :: variant` 생성하기
이것은 할당자를 다루지 않습니다.
struct A {};
struct B { B()=default; B(B const&)=default; B(int){}; };
struct C { C()=delete; C(int) {}; C(C const&)=default; };
struct D { D( std::initializer_list<int> ) {}; D(D const&)=default; D()=default; };
std::variant<A,B> var_ab0; // contains a A()
std::variant<A,B> var_ab1 = 7; // contains a B(7)
std::variant<A,B> var_ab2 = var_ab1; // contains a B(7)
std::variant<A,B,C> var_abc0{ std::in_place_type<C>, 7 }; // contains a C(7)
std::variant<C> var_c0; // illegal, no default ctor for C
std::variant<A,D> var_ad0( std::in_place_type<D>, {1,3,3,4} ); // contains D{1,3,3,4}
std::variant<A,D> var_ad1( std::in_place_index<0> ); // contains A{}
std::variant<A,D> var_ad2( std::in_place_index<1>, {1,3,3,4} ); // contains D{1,3,3,4}