0%

Use meta programming to improve code

從one_of說起

之前寫過這樣的 Code Snippet

1
2
3
4
5
6
7
8
9
10
#include <initializer_list>
enum state_type { IDLE, CONNECTING, CONNECTED, DISCONNECTING, DISCONNECTED };
template <typename ...Ts>
bool is_any_of(state_type s, const Ts& ...ts)
{
bool match = false;
(void)std::initializer_list<int>{(match |= (s == ts), 0)...};
return match;
}
is_any_of(s, IDLE, DISCONNECTING);

這程式碼適用於C++11,如果用C++17可以用fold expression更進一步簡化

1
2
3
4
5
6
enum state_type { IDLE, CONNECTING, CONNECTED, DISCONNECTING, DISCONNECTED };
template <typename ...Ts>
bool is_any_of(state_type s, const Ts& ...ts)
{
return ((s == ts) || ...);
}

能不能更進一步?

variadic non type template

1
2
3
4
5
6
7
enum state_type { IDLE, CONNECTING, CONNECTED, DISCONNECTING, DISCONNECTED };
template <state_type ... states>
bool is_any_of(state_type s)
{
return ((s == states) || ...);
}
is_any_of<IDLE, DISCONNECTING>(s);

可以看出,這個版本將比較值跟貝比教值分開,比原先的版本更容易看出語意,不過問題也很明顯,所以被比較值要在編譯期就決定了,靈活度反而比不上原先的方案,有沒有折衷的方案?

std::tuple

看到Modern Techniques for Keeping Your Code DRY這方案真是眼睛一亮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template <typename ...Ts>
struct any_of : private std::tuple<Ts...> {
using std::tuple<Ts...>::tuple;
template <typename T>
bool operator==(const T& t) const {
return std::apply([&t](const auto& ...ts) { return ((ts == t) | ...); },
static_cast<const std::tuple<Ts...>&>(*this));
}
template <typename T>
friend bool operator==(const T& lh, const any_of& rh) { return rh == lh; }
};

template <typename ...Ts>
any_of(Ts...)->any_of<Ts...>;
any_of(IDLE, DISCONNECTING) == s
s == any_of(IDLE, DISCONNECTING)

用CTAD,std::tuple,std::apply的組合技包裝成威力十足的武器

Add more operation

假設我們現在要加上<的比較

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <typename ...Ts>
struct any_of : private std::tuple<Ts...> {
using std::tuple<Ts...>::tuple;
template <typename T>
bool operator==(const T& t) const {
return std::apply([&t](const auto& ...ts) { return ((ts == t) || ...); },
static_cast<const std::tuple<Ts...>&>(*this));
}

template <typename T>
bool operator<(const T& t) const {
return std::apply([&t](const auto& ...ts) { return ((ts < t) || ...); },
static_cast<const std::tuple<Ts...>&>(*this));
}
};

可以看出兩段程式碼大同小異,能否亙進一步?

Higher order functions

由於兩個operator都是由一系列or operation組成的
因此我們可以把or operation提出來

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
template <typename F, typename ...Ts>
bool or_elements(const F& f, const std::tuple<Ts...>& t) {
return std::apply([&f](const auto& ...ts) { return (f(ts) || ...); }, t);
}

template <typename ...Ts>
struct any_of : private std::tuple<Ts...> {
using std::tuple<Ts...>::tuple;
template <typename T>
bool operator==(const T& t) const {
return or_elements([&t](const auto& v) { return v == t; }, *this);
}

template <typename T>
bool operator<(const T& t) const {
return or_elements([&t](const auto& v) { return v < t; }, *this);
}
};

template <typename ...Ts>
any_of(Ts...)->any_of<Ts...>;

至此any_of的東西就差不多了,那麼來寫all_of吧

all_of implementation

大同小異

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
template <typename F, typename ...Ts>
bool and_elements(const F& f, const std::tuple<Ts...>& t) {
return std::apply([&f](const auto& ...ts) { return (f(ts) && ...); }, t);
}

template <typename ...Ts>
struct all_of : private std::tuple<Ts...> {
using std::tuple<Ts...>::tuple;
template <typename T>
bool operator==(const T& t) const {
return and_elements([&t](const auto& v) { return v == t; }, *this);
}

template <typename T>
bool operator<(const T& t) const {
return and_elements([&t](const auto& v) { return v < t; }, *this);
}
};

template <typename ...Ts>
all_of(Ts...)->all_of<Ts...>;

一般來說做到此就行了,能否亙進一步?

Inheritance

再上去就是炫技了,實用度就不高了,編譯時間又拉長不少

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
struct or_elements {
template <typename F, typename ...Ts>
static bool apply(const F& f, const std::tuple<Ts...>& t) {
return std::apply([&f](const auto& ...ts) { return (f(ts) || ...); }, t);
}
};

struct and_elements {
template <typename F, typename ...Ts>
static bool apply(const F& f, const std::tuple<Ts...>& t) {
return std::apply([&f](const auto& ...ts) { return (f(ts) && ...); }, t);
}
};

template <typename Op, typename ...Ts>
struct op_t : private std::tuple<Ts...> {
using std::tuple<Ts...>::tuple;
template <typename T>
bool operator==(const T& t) const {
return Op::apply([&t](const auto& v) { return v == t; }, *this);
}

template <typename T>
bool operator<(const T& t) const {
return Op::apply([&t](const auto& v) { return v < t; }, *this);
}

template <typename T>
friend bool operator==(const T& lh, const op_t& rh) { return rh == lh; }
template <typename T>
friend bool operator>(const T& lh, const op_t& rh) { return rh < lh; }
};

template <typename ...Ts>
struct any_of : op_t<or_elements, Ts...> {
using op_t<or_elements, Ts...>::op_t;
};

template <typename ...Ts>
struct all_of : op_t<and_elements, Ts...> {
using op_t<and_elements, Ts...>::op_t;
};

template <typename ...Ts>
any_of(Ts...)->any_of<Ts...>;
template <typename ...Ts>
all_of(Ts...)->all_of<Ts...>;

Reference

Modern Techniques for Keeping Your Code DRY
Lambdas: From C++11 to C++20, Part 1
Lambdas: From C++11 to C++20, Part 2