之前有人发现 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