詭異!std::bind in std::bind 編譯失敗
你好,我是雨樂!
上週的某個時候,正在愉快的摸魚,突然群裡丟擲來一個問題,說是編譯失敗,截圖如下:
當時看了報錯,簡單的以為跟之前遇到的原因一樣,隨即提出瞭解決方案,怎奈,短短几分鐘,就被無情打臉,啪啪啪?。為了我那僅存的一點點自尊,趕緊看下原因,順便把之前的問題也回顧下。
好了,言歸正傳(此處應為嚴肅臉),在後面的內容中,將從原始碼角度分析下之前問題的原因,然後再分析下群裡這個問題。
從問題程式碼說起
好了,先說說之前的問題,在Index中,需要有一個更新操作,簡化之後如下:
class Index {
public:
Index() {
update_ = std::bind(&Index::Update, this, std::placeholders::_1, std::bind(&Index::status, this, std::placeholders::_1));
}
std::function<void(const std::string &)> update_;
private:
void Update(const std::string &value, std::function<std::string(const std::string &)> callback) {
if(callback) {
std::cout << "Called update(value) = " << callback(value) << std::endl;
}
}
std::string Status(const std::string &value) {
return value;
}
};
int main() {
Index idx;
idx.update_("Ad0");
return 0;
}
程式碼本身還是比較簡單的,主要在std::bind這塊,std::bind的返回值被用作傳遞給std::bind的一個引數。
編譯之後,報錯提示如下:
錯誤:no match for ‘operator=’ (operand types are ‘std::function<void(const std::__cxx11::basic_string<char>&)>’ and ‘std::_Bind_helper<false, void (Index::*)(const std::__cxx11::basic_string<char>&, std::function<std::__cxx11::basic_string<char>(const std::__cxx11::basic_string<char>&)>), Index*, const std::_Placeholder<1>&, std::_Bind<std::_Mem_fn<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > (Index::*)(const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&)>(Index*, std::_Placeholder<1>)> >::type {aka std::_Bind<std::_Mem_fn<void (Index::*)(const std::__cxx11::basic_string<char>&, std::function<std::__cxx11::basic_string<char>(const std::__cxx11::basic_string<char>&)>)>(Index*, std::_Placeholder<1>, std::_Bind<std::_Mem_fn<std::__cxx11::basic_string<char> (Index::*)(const std::__cxx11::basic_string<char>&)>(Index*, std::_Placeholder<1>)>)>}’)
update_ = std::bind(&Index::Update, this, std::placeholders::_1, std::bind(&Index::status, this, std::placeholders::_1));
經過錯誤排查,本身std::bind()這個是沒問題的,當加上如果對update_進行賦值,就會報如上錯誤,所以問題就出在賦值這塊,即外部std::bind期望的型別與內部std::bind的返回型別不匹配。
定位
單純從程式碼上看,內部std::bind()的型別也沒問題,於是翻了下cppreference,發現了其中的貓膩,當滿足如下情況時候,std::bind()的行為不同(modifies "normal" std::bind behaviour):
• std::reference_wrapper
• std::is_bind_expression
• std::is_placeholder
顯然,我們屬於第二種情況,即__std::is_bind_expression
根據cppreference對第二種情況的描述:
• If the stored argument
arg
is of typeT
for which std::is_bind_expression
上面這塊理解比較吃力,簡言之,如果傳給std::bind()的引數T(在本例中,T為std::bind(&Index::status, this, std::placeholders::_1)
)滿足std::is_bind_expression,那麼就會報上面的錯誤。
為了分析這個原因,研究了下std::bind()(原始碼),下面結合原始碼,分析此次報錯的原因,然後給出解決方案。
bind從實現上分為以下幾類:
• 工具:is_bind_expression、is_placeholder、namespace std::placeholders、_Safe_tuple_element_t和__volget,前兩個用於模板偏特化;
• _Mu:核心模組,此次問題所在。
• _Bind:_Bind和_Bind_result,std::bind的返回型別;
• 輔助:_Bind_check_arity、__is_socketlike、_Bind_helper和_Bindres_helper
因為本文的目的是分析編譯報錯原因,所以僅分析_Mu模組,這是bind()的核心,其他都是圍繞著這個來的,同時它也是本文問題的根結所在,所以分析此程式碼即可(至於其他模組,將在下一篇文章進行分析,從原始碼角度分析bind實現),程式碼如下:
template<typename _Signature>
struct is_bind_expression<const volatile _Bind<_Signature>>
: public true_type { };
template<typename _Arg,
bool _IsBindExp = is_bind_expression<_Arg>::value,
bool _IsPlaceholder = (is_placeholder<_Arg>::value > 0)>
class _Mu;
template<typename _Arg>
class _Mu<_Arg, true, false>
{
public:
template<typename _CVArg, typename... _Args>
auto
operator()(_CVArg& __arg,
tuple<_Args...>& __tuple) const volatile
-> decltype(__arg(declval<_Args>()...))
{
// Construct an index tuple and forward to __call
typedef typename _Build_index_tuple<sizeof...(_Args)>::__type
_Indexes;
return this->__call(__arg, __tuple, _Indexes());
}
private:
// Invokes the underlying function object __arg by unpacking all
// of the arguments in the tuple.
template<typename _CVArg, typename... _Args, std::size_t... _Indexes>
auto
__call(_CVArg& __arg, tuple<_Args...>& __tuple,
const _Index_tuple<_Indexes...>&) const volatile
-> decltype(__arg(declval<_Args>()...))
{
return __arg(std::get<_Indexes>(std::move(__tuple))...);
}
};
首先,需要說明下,std::bind()的實現依賴於std::tuple(),將對應的引數放置於tuple中,最終呼叫會是__arg(std::get<_Indexes>(std::move(__tuple))...)這種方式。
由於函式模板不能偏特化,所以引入了模板類,也就是上面的class _Mu。該類别範本用於轉換繫結引數,在需要的時候進行替換或者呼叫。其有三個引數:
•
_Arg
是一個繫結引數的型別•
_IsBindExp
指示它是否是bind表示式•
_IsPlaceholder
指示它是否是一個佔位符
如果結合本次的示例,那麼_Arg的型別是Index::Update,_IsBindExp為true,而這跟上面的特化template<typename _Arg> class _Mu<_Arg, true, false>
正好相對應。
_Mu有一個成員函式operator()(...),其內部呼叫__call()函式,而__call()函式內部,則會執行__arg(std::get<_Indexes>(std::move(__tuple))...),如果結合文中的Index示例,則這塊相當於執行了Status(value)呼叫。(ps:此處所說的std::bind()是Index示例中巢狀的那個std::bind()操作)。
其實,截止到此處,錯誤原因已經定位出來了,這就是因為最外層的std::bind()引數中,其有一個引數T(此時T的型別為std::bind(&Index::status, this, std::placeholders::_1)),因為滿足std::is_bind_expression這個條件,所以在最外層的std::bind()中,直接對最裡層的std::bind()進行呼叫,而最裡層的std::bind()所繫結的status()的返回型別是std::string,而外層std::bind()所繫結的Update成員函式需要的引數是std::string和std::function<std::string(const std::string &)>,因為引數型別不匹配,所以導致了編譯錯誤。
解決
方案一
既然前面分析中,已經將錯誤原因說的很明白了(型別不匹配),因此,我們可以將Update()函式重新定義:
void Update(const std::string &value, std::function<std::string(const std::string &)> callback) {
// do sth
}
編譯透過!
方案二
既然編譯器強調了型別不匹配,那麼嘗試將內層的std::bind()進行型別轉換:
update_ = std::bind(&Index::Update, this, std::placeholders::_1, static_cast<std::function<std::string(const std::string &)>>(std::bind(&Index::status, this, std::placeholders::_1)));
編譯透過!
方案三
在前面的兩個方案中,方案一透過修改Update()函式的引數(將之前的第二個引數從std::function()修改為std::string
),第二個方案則透過型別轉換,即將第二個std::bind()的型別強制轉換成Update()函式需要的型別,在本小節,將探討一種更為通用的方式。
在方案二中,使用static_cast<>進行型別轉換的方式,來解決編譯報錯問題,不妨以此為突破點,只有在std::is_bind_expression<T>::value == TRUE
的時候,才需要此類轉換,因此藉助SFINAE特性進行實現,如下:
template<typename T>
class Wrapper : public T {
public:
Wrapper(const T& t) : T(t) {}
Wrapper(T&& t) : T(std::move(t)) {}
};
template<typename T, typename U = typename std::decay<T>::type >
typename std::enable_if< !std::is_bind_expression< U >::value, T&& >::type Transfer(T&& t) {
return std::forward<T>(t);
}
template<typename T, typename U = typename std::decay<T>::type >
typename std::enable_if< std::is_bind_expression< U >::value, Wrapper< U > >::type Transfer(T&& t) {
return Wrapper<U>(std::forward<T>(t));
}
相應的,對std::bind()那行也進行修改,程式碼如下:
update_ = std::bind(&Index::Update, this, std::placeholders::_1, Transfer(std::bind(&Index::status, this, std::placeholders::_1)));
再次進行編譯,成功?。
群裡的問題
好了,接著回到群裡的那個問題。
為了分析該問題,私下跟提問的同學進行了友好交流,才發現他某個函式是過載的,而該過載函式的引數為引數個數和型別不同的std::function(),下面是簡化後的程式碼:
#include <functional>
#include <iostream>
#include <string>
using Handler = std::function<void(int, const std::string &)>;
using SeriesHandler = std::function<void(int, const std::string &, bool)>;
void reg(int n, const std::string &str) {
std::cout << "n = " << n << ", str = " << str << std::endl;
}
void fun(const std::string &route, const Handler &handler) {
handler(1, "2");
}
void fun(const std::string &route, const SeriesHandler &handler) {
}
int main() {
fun("/abc", std::bind(reg, std::placeholders::_1, std::placeholders::_2));
return 0;
}
編譯器報錯如下:
test.cc:41:75: 錯誤:呼叫過載的‘fun(const char [5], std::_Bind_helper<false, void (&)(int, const std::__cxx11::basic_string<char>&), const std::_Placeholder<1>&, const std::_Placeholder<2>&>::type)’有歧義
fun("test", std::bind(reg, std::placeholders::_1, std::placeholders::_2));
^
tt.cc:32:6: 附註:candidate: void fun(const string&, const Handler&)
void fun(const std::string &route, const Handler &handler) {
^
tt.cc:36:6: 附註:candidate: void fun(const string&, const SHandler&)
void fun(const std::string &route, const SHandler &handler) {
^
好了,先看下cppreference對這個問題的回答:
If some of the arguments that are supplied in the call to
g()
are not matched by any placeholders stored ing
, the unused arguments are evaluated and discarded.
也就是說傳給g()函式的引數在必要的時候,可以被丟棄,舉例如下:
void fun() {
}
auto b = std::bind(fun);
b(1, 2, 3); // 成功
再看一個例子:
#include <functional>
void f() {
}
int main() {
std::function<void(int)> a = std::bind(f);
std::function<void()> b = std::bind(f);
a(1);
b();
return 0;
}
綜上兩個例子,做個總結,程式碼如下:
void f() {}
void f(int a) {}
auto a = std::bind(f)
auto b = std::bind(f, std::placeholders::_1)
在上面兩個bind()中,第一個支援初始化型別(即a的型別)為std::function<void(arg...)>,其中arg的引數個數為0到n(sizeof...(arg) >= 0);而第二個bind()其支援的初始化型別(即b的型別)為std::function<void(arg...)>,其中arg的引數個數為1到n(sizeof...(arg) >= 1)。
那麼可以推測出:
auto c = std::bind(reg, std::placeholders::_1, std::placeholders::_2);
c支援的引數個數>=2,在編譯器經過測試,編譯正確~~
那麼回到群裡的問題,在main()函式中:
fun("/abc", std::bind(reg, std::placeholders::_1, std::placeholders::_2));
其有一個引數std::bind()(是不是跟前面的程式碼類似?),這個std::bind()匹配的std::function()的引數個數>=2,即std::bind()返回的型別支援的引數個數>=2,而fun()有兩個過載函式,其第二個引數其中一個為2個引數的std::function(),另外一個為3個引數的std::function(),再結合上面的內容,main()函式中的fun()呼叫顯然都匹配兩個過載的fun()函式,這是,編譯器不知道使用哪個,所以乾脆報錯。
好了,既然知道原因了,那就需要有解決辦法,一般有如下幾種:
• 使用lambda替代std::bind()
• 靜態型別轉換,即上一節中的static_cast
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024923/viewspace-2938824/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- std::bind與std::ref, why and how
- c++11:std::bindC++
- std::vector 和 std::list 區別
- c++11:std::boolalpha、std::noboolalphaC++
- python 編譯失敗Python編譯
- std::reserve和std::resize的區別
- `std::packaged_task`、`std::thread` 和 `std::async` 的區別與聯絡Packagethread
- [譯] Javascript: call()、apply() 和 bind()JavaScriptAPP
- (C++11/14/17學習筆記):std::atomic續、std::async與std::thread對比C++筆記thread
- laravel-mix編譯失敗Laravel編譯
- 記錄 openssl 證書驗證失敗的詭異問題
- C++ 標準庫 std::set std::multiset swap()的使用C++
- 【C++併發實戰】(三) std::future和std::promiseC++Promise
- 【譯】對Rust中的std::io::Error的研究RustError
- std::make_shared
- C++(std::vector)C++
- std::count 函式函式
- ODRDMS_GOV_STDGo
- JavaScript bind()JavaScript
- 智慧指標思想實踐(std::unique_ptr, std::shared_ptr)指標
- std::function用法學習Function
- 理解 std::declval 和 decltype
- zend_std_read_property
- hive原始碼編譯(失敗記錄)Hive原始碼編譯
- 【譯】理解this及call,apply和bind的用法APP
- [譯] 如何在 JavaScript 中使用 apply(?),call(?),bind(➰)JavaScriptAPP
- 透徹理解C++11新特性:右值引用、std::move、std::forwardC++Forward
- C++ 智慧指標詳解: std::unique_ptr 和 std::shared_ptrC++指標
- v-bind
- 手寫bind
- 詳解bind
- this、apply、call、bindAPP
- javascript bind polyfillJavaScript
- DNS和BINDDNS
- 寶塔php編譯安裝fileinfo失敗PHP編譯
- AndroidKiller反編譯失敗的處理方法Android編譯
- bind 127.0.0.1 ::1 和 bind 127.0.0.1 有什麼區別127.0.0.1
- std::async的使用總結