剛剛看到一篇博客,說 std::bind 無法綁定正確的重載函數(shù)。這里的問題并不是 std::bind 能力不足,而是將函數(shù)名傳遞給 std::bind 時(shí)編譯器無法取到這個(gè)函數(shù)的地址(也就是符號(hào),編譯器會(huì)先解析成符號(hào),鏈接器再替換為地址),因?yàn)橛卸鄠€(gè)重載函數(shù)都是這個(gè)名字。核心問題是無法通過函數(shù)名取到想要的重載函數(shù)地址。就像下面的代碼無法編譯通過:
創(chuàng)新互聯(lián)公司專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于網(wǎng)站制作、網(wǎng)站建設(shè)、王屋網(wǎng)絡(luò)推廣、小程序制作、王屋網(wǎng)絡(luò)營銷、王屋企業(yè)策劃、王屋品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運(yùn)營等,從售前售中售后,我們都將竭誠為您服務(wù),您的肯定,是我們最大的嘉獎(jiǎng);創(chuàng)新互聯(lián)公司為所有大學(xué)生創(chuàng)業(yè)者提供王屋建站搭建服務(wù),24小時(shí)服務(wù)熱線:18982081108,官方網(wǎng)址:www.cdcxhl.com
#include
void f()
{
std::cout << "f 1" << std::endl;
}
void f(int x)
{
std::cout << "f 2 " << x << std::endl;
}
int main()
{
auto p = &f;
}
編譯錯(cuò)誤:
/home/abc/cpplearn/overload_func.cpp: In function ‘int main()’:
/home/abc/cpplearn/overload_func.cpp:15:15: error: unable to deduce ‘a(chǎn)uto’ from ‘& f’
15 | auto p = &f;
| ^
/home/abc/cpplearn/overload_func.cpp:15:15: note: couldn’t deduce template parameter ‘a(chǎn)uto’
有沒有什么比較完美的解決辦法呢?我覺得一定有,因?yàn)?C 語言沒有函數(shù)重載,函數(shù)地址作為實(shí)參也是常規(guī)操作。相比之下,C++ 引入了函數(shù)重載,卻無法取到函數(shù)地址,這就很尷尬。C++ 設(shè)計(jì)者肯定也想到了這個(gè)問題。
于是查閱了 cppreference.com,看到了 Address of an overloaded function。函數(shù)名的重載解析除了發(fā)生在函數(shù)調(diào)用的時(shí)候,也會(huì)發(fā)生在以下 7 種語境:
# | Context | Target |
---|---|---|
1 | initializer in a declaration of an object or reference | the object or reference being initialized |
2 | on the right-hand-side of an assignment expression | the left-hand side of the assignment |
3 | as a function call argument | the function parameter |
4 | as a user-defined operator argument | the operator parameter |
5 | the return statement |
the return type of a function |
6 | explicit cast or static_cast argument |
the target type of a cast |
7 | non-type template argument | the type of the template parameter |
當(dāng)函數(shù)名存在于這 7 種語境時(shí),會(huì)發(fā)生重載解析,并且會(huì)選擇與 Target 類型匹配的那個(gè)重載函數(shù)。這里就不一一考察這 7 種語境了,有興趣可以自己查閱 cppreference.com。這里重點(diǎn)考察第 3 種和第 6 種。
先看第 3 種語境。當(dāng)函數(shù)名作為函數(shù)調(diào)用的實(shí)參時(shí),重載解析會(huì)選擇和形參類型相匹配的版本。也就是說,下面的代碼會(huì)如期運(yùn)行:
#include
void f()
{
std::cout << "f 1" << std::endl;
}
void f(int x)
{
std::cout << "f 2 " << x << std::endl;
}
void call(void p(int)) {
p(1);
}
int main()
{
call(f);
}
這段代碼輸出:
f 2 1
回到最初的問題,std::bind 也是函數(shù),為什么無法正常編譯呢?直接分析下面代碼的編譯錯(cuò)誤信息:
#include
#include
void f()
{
std::cout << "f 1" << std::endl;
}
void f(int x)
{
std::cout << "f 2 " << x << std::endl;
}
int main()
{
auto new_func = std::bind(f, std::placeholders::_1);
new_func(66);
}
編譯錯(cuò)誤:
/home/abc/cpplearn/overload_func.cpp: In function ‘int main()’:
/home/abc/cpplearn/overload_func.cpp:16:30: error: no matching function for call to ‘bind(, const std::_Placeholder<1>&)’
16 | auto new_func = std::bind(f, std::placeholders::_1);
| ~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~
可以看到,std::bind 準(zhǔn)確地說是一個(gè)函數(shù)模板。它要根據(jù)其參數(shù)進(jìn)行模板實(shí)參推導(dǎo),再替換模板形參進(jìn)行實(shí)例化(Instantiation),產(chǎn)生和普通函數(shù)類似的匯編代碼。std::bind 進(jìn)行實(shí)例化的時(shí)候,函數(shù) f
還沒有進(jìn)行重載解析,其類型為
。std::bind 無法進(jìn)行實(shí)例化。怎樣修改可以解決這個(gè)問題呢?
可以利用第 6 個(gè)語境,也就是顯示轉(zhuǎn)換或 static_cast。重載解析會(huì)選擇與它們的目標(biāo)類型相匹配的版本。下面的代碼會(huì)如期運(yùn)行:
#include
#include
void f()
{
std::cout << "f 1" << std::endl;
}
void f(int x)
{
std::cout << "f 2 " << x << std::endl;
}
int main()
{
auto new_func = std::bind((void(*)(int))f, std::placeholders::_1);
new_func(66);
}
這段代碼輸出:
f 2 66
還有一種更加巧妙的辦法,依然是利用第 3 種語境。既然 std::bind 的第一個(gè)模板實(shí)參的推導(dǎo),和 f 的重載解析相矛盾。為什么不直接解決這個(gè)矛盾,將第一個(gè)模板實(shí)參改為顯示指定?來看下面的代碼:
#include
#include
void f()
{
std::cout << "f 1" << std::endl;
}
void f(int x)
{
std::cout << "f 2 " << x << std::endl;
}
int main()
{
auto new_func = std::bind(f, std::placeholders::_1);
new_func(66);
}
這段代碼如期輸出:
f 2 66