之前有人發現 C++11/14/17 標準有個漏洞,
透過 friend + template + noexcept 可以讓一個 constexpr expression 有不同的值
原理大概是這樣的:
==============================================================================
constexpr function 可以是有定義或沒定義,這會影響到 noexcept 和 SFINAE 的結果:
constexpr void f();
constexpr void g() {};
constexpr bool a = noexcept(f()); // false
constexpr bool b = noexcept(g()); // true
如果我們可以控制一個 constexpr function 有無定義,
就可以將他當作一個 bit 來使用,
這點可以用 template + friend function 給定義達成:
constexpr void flag(int); // 宣告 flag
template<typename T>
struct writer {
friend void flag(T) {}
// 當 writer<int> 被 instatinate 時,會定義 void flag(int)
};
constexpr bool a = noexcept(flag(0)); // false
writer<int> w; // instatinate `writer<int>`
constexpr bool b = noexcept(flag(0)); // true
static_assert(a != b);
int main() {
std::cout << a << ' ' << b << std::endl; // 0 1
}
http://coliru.stacked-crooked.com/a/a6cc0faeb9f215c8
有了這鬼東西以後,就可以做出 compile-time 的 counter :
static_assert(next() != next());
http://coliru.stacked-crooked.com/a/648448a3a8d03275
稍微包裝一下就可以做出 constify 的功能:
begin_mutable_region(r); // 宣告 r
auto x = r::make<int>(1);
auto v = r::make<std::vector<int>>();
x.get() = 42; // 修改 x
v.get().emplace_back(42); // 修改 v
end_mutable_region(r); // 之後不可再修改透過 r::make 製造出來的東西
x.get() = 43; // Error
v.get().clear(); // Error
std::cout << x << std::endl; // 還是可以透過 const int& 存取 x
http://coliru.stacked-crooked.com/a/3d63a2e82c173055
其原理就是透過設定 r 當作旗標,若 r 被設立時就關閉 non-const 的 overload
不過這個技巧被 C++ 委員會認為是 ill-formed:
https://wg21.cmeerw.net/cwg/issue2118
所以未來應該會修改標準禁止這鬼東西,至於具體要怎麼做現在似乎還沒有提案
參考:
Non-constant constant expressions in C++:
http://b.atch.se/posts/non-constant-constant-expressions/
How to implement a constant-expression counter in C++
http://b.atch.se/posts/constexpr-counter/
用 stateful metaprogramming 模擬 Rust 的 borrow checker:
https://medium.com/@bhuztez/db4b5e94449f
https://github.com/bhuztez/borrow