API Design
看了lexy的程式碼之後,對其API Design很有興趣
其中有一個設計
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
| template <typename Fn> struct _parse_state : _detail::placeholder_base { LEXY_EMPTY_MEMBER Fn _fn; template <typename State, typename... Args> constexpr decltype(auto) operator()(State& state, const _detail::tuple<Args...>&) const { static_assert(!std::is_same_v<State, _detail::no_bind_state>, "lexy::parse_state requires that a state is passed to lexy::parse()"); return _detail::invoke(_fn, state); } }
template <> struct _parse_state<void> : _detail::placeholder_base { template <typename State, typename... Args> constexpr decltype(auto) operator()(State& state, const _detail::tuple<Args...>&) const { static_assert(!std::is_same_v<State, _detail::no_bind_state>, "lexy::parse_state requires that a state is passed to lexy::parse()"); return state; }
template <typename Fn> constexpr auto map(Fn&& fn) const { return _parse_state<std::decay_t<Fn>>{{}, LEXY_FWD(fn)}; } };
constexpr auto parse_state = _parse_state<void>{};
|
這API的重點
- 我們只希望有一個Root
parse_state
- 只能透過 Util function
map
,可以創建另一個placeholder_base的subclass
lexy採用的是Partial template specialization方案來應對這個設計,不過這方案有個缺點,大量重複的程式碼
以上面的例子來看,operator()
的內容幾乎一樣,如果功能更多的話,可能會有更多類似但卻不相同的函數出現,可能增加維護的難度deduce this
deduce this的教學文章很多了,拾人牙慧也沒什麼意思
雖然不確定是否是最佳解,不過可以試試看設計這樣的API1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| struct Base { template <typename Self> decltype(auto) operator()(this Self&& self) { if constexpr (std::is_same_v<std::decay_t<Self>, Base>) { } else { } } template <typename Self> requires std::is_same_v<std::decay_t<Self>, Base> void test1(this Self&& self) { } };
struct Derived : public Base {}; constexpr auto parse_state = Base{};
|
這邊的map跟lexy上面的map差不多
- 只能透過
parse_state
來使用util function map
而operator()
就能共用大部分的邏輯了
不過這方法也是有缺點,對邊義氣版本的要求很高
- gcc目前要14才有支援
- clang目前也是到18才支持
- MSVC至少要17.2
CRTP
在寫完後不久,才想起CRTP也可以解決這問題
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| template <typename Derived = void> struct Base { decltype(auto) operator()() const { if constexpr (std::is_void_v<Derived>) { static_assert(false); } else { } }
void map() const requires std::is_void_v<Derived> { } };
struct Derived {};
|
不過比起deduce this,可以要麻煩一點
Reference