從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