0%

deduce this for API design

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的教學文章很多了,拾人牙慧也沒什麼意思
    雖然不確定是否是最佳解,不過可以試試看設計這樣的API
    1
    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) {
    // Shared common logic
    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