5 min read

std::visit와 std::variant을 이용한 오버로딩

이 내용은 대부분은 https://www.bfilipek.com/2018/09/visit-variants.html 을 참조하였습니다.

std::variant 클래스는 간단히 정의하자면 안전한 union 버전이라고 볼수있다.
union의 최대의 단점은 타입의 값을 집어넣었을때 해당값이 int,string 알수 없다.
이 단점을 회피하기위해 C++ 17에는 std::variant 클래스가 추가되었다.
자세한 설명은 보자.

http://occamsrazr.net/tt/323 https://en.cppreference.com/w/cpp/utility/variant

std::visit함수는  std::variant 클래스에 담겨있는 타입에 맞는 operator을 호출을 해주는 함수이다. 자세한건 아래의 코드를 보자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// ConsoleApplication1.cpp : Defines the entry point for the console application.
//
 
#include “stdafx.h”
#include <iostream>
#include <utility>
#include <variant>
 
 
struct Computer{ };
struct Mp3 {};
struct HandPhone {};
struct hand {};
 
struct Buy {
    void operator()(Mp3 &obj) { std::cout << “Mp3” << std::endl; }
    void operator()(Computer &computer) { std::cout << “Computer” << std::endl; }
    void operator()(hand &computer) { std::cout << “hand” << std::endl; }
    void operator()(HandPhone &computer) { std::cout << “HandPhone” << std::endl; }
 
 
};
int main() {
    std::variant<Computer, Mp3, HandPhone, hand> weapon{Mp3()};
    std::visit(Buy(), weapon);
}
 
cs

해당 코드를 실행시켜보자. 만약에 VS2017을 사용해도 컴파일이 안되면 http://www.kudryavka.me//?p=268 글을 참조하자.

결과와 알수 있듯이 std::variant의 다양한 타입들중에 현재안에 들어있는 타입에 맞는 오버로딩을 호출하여 std::visit가 호출을 해주는거를 알수있다.

또한 Buy 오버로딩 클래스를 정의하지않고 람다표현식을 이용하여 아래와같이 쓸수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <utility>
#include <variant>
 
 
template <class …Ts> struct overload : Ts…{using Ts::operator()…; };
template <class …Ts> overload(Ts…)>overload<Ts…>;
 
struct Computer{ };
struct Mp3 {};
struct HandPhone {};
struct hand {};
 
int main() {
    std::variant<Computer, Mp3, HandPhone, hand> weapon{Mp3()};
    std::visit(overload{
        [](Computer&) {std::cout << “Computer” << std::endl; },
        [](Mp3&) {std::cout << “Mp3” << std::endl; },
        [](HandPhone&) {std::cout << “HandPhone” << std::endl; },
        [](hand&) {std::cout << “hand” << std::endl; }
    }, weapon);
}
 
cs

위의 코드를 실행 시키면 아까 전과 동일한 결과가 출력이 된다.

6~7줄의 overload(C++17문법)의 대해 이해가 안가면 다음 링크를 참조하도록 하자.

https://arne-mertz.de/2018/05/overload-build-a-variant-visitor-on-the-fly/8/05/overload-build-a-variant-visitor-on-the-fly/

https://arne-mertz.de/2017/06/class-template-argument-deduction/

또한 std::visit에 더 많은 변수를 담을수 있다. 아래의 함수의 선언을 보게되면 가변 템플릿을 사용한것을 알수있다.
template <class Visitor, class... Variants>
constexpr ReturnType visit(Visitor&& vis, Variants&&... vars);

가변 템플릿의 특성을 이용하여 다음과 같이 작성할수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using namespace std;
 
template <class …Ts> struct overload : Ts…{using Ts::operator()…; };
template <class …Ts> overload(Ts…)>overload<Ts…>;
 
struct Computer{ };
struct Mp3 {};
struct HandPhone {};
struct hand {};
 
int main() {
    std::variant<intfloatcharstring> v1{ “asdasda”s };
    std::variant<intfloatcharstring> v2{ 10 };
 
    std::visit(overload{
        [](int a, int b) {},
        [](int a, float b) {},
        [](int a, char b) {},
        [](float a, int b) {},
        [](auto a, auto b) {std::cout << “auto” << std::endl; }, //나머지 호출
        }, v1, v2);
}
 
cs

마지막에 auto를 둔 이유는 매칭이 되지않았을경우 해당 operator을 호출하도록 유도한 것이다.  

참조 링크 :

https://www.bfilipek.com/2018/09/visit-variants.html 

https://gist.github.com/utilForever/824a1bcb3a6b589b1c887e7ede930976