namespace
由於繼承自C語言,所以會遇到像這樣的問題
1 | // my_std.h |
來自於不同的Library,且提供不同的實作,在使用上會出現一些問題
而C語言時代的解法就是對Function Name加料
1 | // my_std.h |
而C++做的事情差不多,用namespace隔開
1 | // my_std.h |
ADL
全名是Argument-Dependent Lookup
只要有一個參數在函數的命名空間內,在使用的時候就不用加namespace prefix
在ADL只關心函數,不包含Function Object,這點在之後會用到
1 | namespace A |
如果沒有ADL,最後那行只能這樣寫了
1 | std::operator<<(std::cout, 1); |
應該沒人會喜歡
Example for std::swap
這是拓展問題的最好範例
1 | namespace std { |
如果我們要對自己的class做swap動作時,該怎麼做
1 | namespace My { |
直覺的寫法可以這樣做
1 | ```cpp |
這樣寫是Undefined Beahvior
而另外一種做法是
1 | template<> |
不過如果是My::A<T>
的話就不管用了
而比較常用的手法,就是利用ADL
1 | void fun(...); // 1 |
呼叫的foo(a)
時,會考慮2和3,1是因為在Code的namespace已經找到一個fun了,部會在往上層的scope去尋找
利用ADL two-step
的手法來拓展我們的std::swap
1 |
|
在這個範例當中,呼叫swap的時候沒加上namespace,而讓std::swap
注入當今的Scope下,如果可以透過ADL找到對應的函數,則用特化版的函數,不然就用原先的std::swap
做預設值
Drawback on ADL two-step
最大的問題在於
1 | using std::swap; |
可能一不小心就寫成
1 | std::swap(a1, a2); |
不會報錯,頂多是效能差
另外一個比較大的問題是這個
1 | namespace __my_std_impl |
在__my_std_impl::distance(b, b)
的地方會報錯
原因在於__distance_impl
階段會進行ADL動作,在box的定義上尋找是否有__distance_impl
的函數,因找到incomplete value,故報錯
一種可能的解法就是加上namespace
1 | template<typename T> |
Customization Point Object
兩階段ADL的最大問題就是容易誤用
因此叫Standlard library來幫你做這件事
其中最簡單的CPO就長這樣
1 | namespace std::ranges { |
這裡的swap是個constexpr object,而不是個function,不過他是一個functor,因此可以適用於所有std::swap的環境下
CPO還有一個優勢,它是一個object,所以它能夠這樣用
1 | some_ranges | views::transform(ranges::begin) |
而
1 | some_ranges | views::transform(std::begin) |
這樣用不合法,因為它是個template function
Niebloids
Niebloids是要解決另外一個問題,去除掉不想要的ADL candicate
禁用的方法就是讓它成為CPO
以下是StackOverflow的範例
1 |
|
如果找到的是function object,則不會使用ADL
tag_invoke
根據libunifex裡面的描述,一樣是透過ADL,要解決以下兩個問題
- Each one internally dispatches via ADL to a free function of the same name, which has the effect of globally reserving that identifier (within some constraints). Two independent libraries that pick the same name for an ADL customization point still risk collision.
- There is occasionally a need to write wrapper types that ought to be transparent to customization. (Type-erasing wrappers are one such example.) With C++20’s CPOs, there is no way to generically forward customizations through the transparent wrap
比較大的問題是第一點,由於透過ADL尋找函數,所以每個namespace下都需要將函數名稱當作保留字
1 | namespace std::range { |
也就是你用了swap當CPO之後,其他地方都要保留swap當作保留字不能使用,tag_invoke就是為了這點而生的
參考C++11 tag_invoke的實作 duck_invoke
1 |
|
主要的作法是
- 需要一個CPO參數,以上的範例是
formula
- 只需要一個tag_invoke function,不過可以Overloading,對不同的CPOj做不同的處理
不過tag_invoke製造了其他問題,難以理解且囉嗦
Future
由於Executors跳票了,所以tag_invoke也不一定是最終解決方案
目前有其他提案,不過會不會被接受也在未定之天
詳細可以找找P2547R0來研究
Reference
如何理解 C++ 中的 定制点对象 这一概念?为什么要这样设计?
c++ execution 与 coroutine (一) : CPO与tag_invoke
C++特殊定制:揭秘cpo与tag_invoke!
Customization Points
Argument-dependent lookup - cppreference.com
Why tag_invoke is not the solution I want (brevzin.github.io)
What is a niebloid?
ADL,Concepts与扩展C++类库带来的思考
Duck Invoke — tag_invoke for C++11