剛剛看到一篇博客,說 std::bind 無法系結正確的多載函式,這里的問題并不是 std::bind 能力不足,而是將函式名傳遞給 std::bind 時編譯器無法取到這個函式的地址(也就是符號,編譯器會先決議成符號,聯結器再替換為地址),因為有多個多載函式都是這個名字,核心問題是無法通過函式名取到想要的多載函式地址,就像下面的代碼無法編譯通過:
#include <iostream>
void f()
{
std::cout << "f 1" << std::endl;
}
void f(int x)
{
std::cout << "f 2 " << x << std::endl;
}
int main()
{
auto p = &f;
}
編譯錯誤:
/home/abc/cpplearn/overload_func.cpp: In function ‘int main()’:
/home/abc/cpplearn/overload_func.cpp:15:15: error: unable to deduce ‘auto’ from ‘& f’
15 | auto p = &f;
| ^
/home/abc/cpplearn/overload_func.cpp:15:15: note: couldn’t deduce template parameter ‘auto’
有沒有什么比較完美的解決辦法呢?我覺得一定有,因為 C 語言沒有函式多載,函式地址作為實參也是常規操作,相比之下,C++ 引入了函式多載,卻無法取到函式地址,這就很尷尬,C++ 設計者肯定也想到了這個問題,
于是查閱了 cppreference.com,看到了 Address of an overloaded function,函式名的多載決議除了發生在函式呼叫的時候,也會發生在以下 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 |
當函式名存在于這 7 種語境時,會發生多載決議,并且會選擇與 Target 型別匹配的那個多載函式,這里就不一一考察這 7 種語境了,有興趣可以自己查閱 cppreference.com,這里重點考察第 3 種和第 6 種,
先看第 3 種語境,當函式名作為函式呼叫的實參時,多載決議會選擇和形參型別相匹配的版本,也就是說,下面的代碼會如期運行:
#include <iostream>
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 也是函式,為什么無法正常編譯呢?直接分析下面代碼的編譯錯誤資訊:
#include <iostream>
#include <functional>
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);
}
編譯錯誤:
/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(<unresolved overloaded function type>, const std::_Placeholder<1>&)’
16 | auto new_func = std::bind(f, std::placeholders::_1);
| ~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~
可以看到,std::bind 準確地說是一個函式模板,它要根據其引數進行模板實參推導,再替換模板形參進行實體化(Instantiation),產生和普通函式類似的匯編代碼,std::bind 進行實體化的時候,函式 f 還沒有進行多載決議,其型別為<unresolved overloaded function type>,std::bind 無法進行實體化,怎樣修改可以解決這個問題呢?
可以利用第 6 個語境,也就是顯示轉換或 static_cast,多載決議會選擇與它們的目標型別相匹配的版本,下面的代碼會如期運行:
#include <iostream>
#include <functional>
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 的第一個模板實參的推導,和 f 的多載決議相矛盾,為什么不直接解決這個矛盾,將第一個模板實參改為顯示指定?來看下面的代碼:
#include <iostream>
#include <functional>
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
本文來自博客園,作者:mkckr0,轉載請注明原文鏈接:https://www.cnblogs.com/mkckr0/p/16353596.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/487410.html
標籤:C++
上一篇:初識C++05:運算子多載
