0%

Variant in C++ 17

從Boost Variant談起

1
2
3
#include <boost/variant.hpp>
boost::variant<int, std::string> v;
v = "Hello World!";

boost::get

使用boost::get需要給出正確型別,不然會拋出 Exception

1
2
std::cout << boost::get<std::string>(v) << std::endl; // Hello World!
std::cout << boost::get<int>(v) << std::endl; // terminate called after throwing an instance of 'boost::bad_get'

Use RTTI

1
2
3
4
5
6
7
8
void var_print(const boost::variant<int, std::string> &v)
{
if (v.type() == typeid(int)) {
std::cout << boost::get<int>(v) << std::endl;
} else if (v.type() == typeid(std::string)) {
std::cout << boost::get<std::string>(v) << std::endl;
}
}

每增加一種型別就要修改程式碼,並且影響性能

Visitor Pattern

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class var_visitor : public boost::static_visitor<void>
{
public:
void operator()(int i) const {
std::cout << i << std::endl;
}
void operator()(const std::string& str) const {
std::cout << str << std::endl;
}
// the default case:
template <typename T> void operator()(T const &) const {
std::cout << "FALLBACK: " << __PRETTY_FUNCTION__ << "\n";
}
};
boost::apply_visitor(var_visitor(), v);

不過C++17的visit功能更強,實現更優雅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class var_visitor
{
public
void operator()(int i) const {
std::cout << i << std::endl;
}
void operator()(const std::string& str) const {
std::cout << str << std::endl;
}
// the default case:
template <typename T> void operator()(T const &) const {
std::cout << "FALLBACK: " << __PRETTY_FUNCTION__ << "\n";
}
};
visit(var_visitor(), v);

智逾期他的visit方式就先打住,有空在研究,介紹variant可以作些什麼

Stack-based run-time polymorphism

傳統基於heap based的polymorphism都是這麼做的
– 分配一塊記憶體
– 將物件創造於記憶體上
– 根據vtbl呼叫virtual function
– 歸還記憶體

所以一般都會寫出這樣的程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct Base {
virtual ~Base() = default;
virtual void func() const = 0;
};
struct Der1 : public Base {
void func() const override { cout << "Der1" << endl; }
};
struct Der2 : public Base {
void func() const override { cout << "Der2" << endl; }
};

unique_ptr<Base> create(int v)
{
if (v)
return make_unique<Der1>();
else
return make_unique<Der2>();
}

auto test = [](const Base &obj) {
obj.func();
};
auto obj = create(0);
test(*obj);

而基於stack的作法,省去了最前面跟最後面的步驟,因此速度更快,如果有機會devirtualize的話,連vtbl都可以不需要

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using derv = variant<Der1, Der2>;
erv create(int v)
{
if (v)
return Der1();
else
return Der2();
}

template <typename BaseType, typename ... Types>
BaseType& cast_to_base(variant<Types ...>& v)
{
return visit([](BaseType& arg) -> BaseType& { return arg; }, v);
}

auto test = [](const Base &obj) {
obj.func();
};
derv obj = create(0);
test(cast_to_base<Base>(obj));

Reference

variant in C++14
浅谈boost.variant的几种访问方式
让boost.variant支持lambda表达式访问
default visitor function for boost::variant
visiting variants using lambdas - part 1
visiting variants using lambdas - part 2
Polymorphism Polymorphism