0%

Condition compilation and C++20 Module

Condition compilation相信大家都很熟了,相信不用多介紹了,以下是一個示範案例

1
2
3
4
5
6
7
8
9
10
struct Proxy {
#ifdef FEATURE_ENABLE
int v;
#endif
void do_something() {
#ifdef FEATURE_ENABLE
v = 42;
#endif
}
};

不過在C++20 Module的世界中,要求有穩定,一致的ABI
Preprocessor格格不入,必須想個方法轉換,以下是我的思路

Step1: Create static constexpr variable

1
2
3
4
5
#ifdef FEATURE_ENABLE
static constexpr bool enable_feature_v = true;
#else
static constexpr bool enable_feature_v = false;
#endif

這一步就是將Compiler所收到的Definition變成constexpr variable

Step2: Failed attempt

試著翻譯上面的程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <bool>
struct ProxyImpl {};

template <>
struct ProxyImpl<true> {
int v;
};

struct Proxy : public ProxyImpl<enable_feature_v> {
void do_something() {
if constexpr (enable_feature_v) {
v = 42;
}
}
};

enable_feature_vfalse的時候會報錯

1
2
3
4
5
6
7
**<source>:** In member function '**void Proxy::**do_something****()':

**<source>:14:25:** **error:** '**v**' was not declared in this scope

14 | **v** = 42;

|

不管enable_feature_v值為何,Proxy底下一定要有個member v存在,就算繼承改成member也是同樣的情形

Step3: std::optional

在Runtime決定狀態,多了一點Runtime overhead,不過至少可行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct ProxyImpl {
    int v;
};

struct Proxy {
    std::optional<ProxyImpl> v;
    Proxy() {
        if constexpr (enable_feature_v) v.emplace();
    }

    void do_something() {
        if constexpr (enable_feature_v) {
            (*v).v = 42;
        }
    }
};

Step4: Revisit template specialization

重回失敗的第二步,不過這次是定義函數為操作單位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
template <bool>
struct ProxyImpl {
    void update(int) {}
};

template <>
struct ProxyImpl<true> {
    int v;
    void update(int newV) { v = newV; }
};

struct Proxy : public ProxyImpl<enable_feature_v> {
    void do_something() {
        if constexpr (enable_feature_v) {
            update(42);
        }
    }
};

根據輸出的Assembly Code,空的update會被編譯器整個偵測到,完全消失
不過這方法也很麻煩,要同時維護好幾份的Function Set,就算是空的也要維護

Step5: Condtional Proxy

如果將feature flag帶入Type裡面,則可以解決上面的問題,且帶來其他的問題

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
template <bool>
struct ProxyImpl {};

template <>
struct ProxyImpl<true> {
    int v;
};

template <bool enable_feature>
struct Proxy {
    ProxyImpl<enable_feature> v;
    void do_something() {
        if constexpr (enable_feature) {
            v.v = 42;
        }
    }
};

int main()
{
Proxy<enable_feature_v> p;
p.do_something();
}

因為他是template class,所以在傳統的C++,只能放在Header Unit,如果是Module世界的話,只能放在Module interface unit,不能放到Module Implementation Unit

Conclusion

目前沒有什麼十全十美的好方法,可能需要進一步的探索