可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Consider this simple example code:
#include <functional>
#include <iostream>
void f(bool _switch) {
std::cout << "Nothing really" << std::endl;
}
void f(std::function<double (int)> _f) {
std::cout << "Nothing really, too" << std::endl;
}
int main ( int argc, char* argv[] ) {
f([](int _idx){ return 7.9;});
return 0;
}
It fails to compile:
$ g++ --std=c++11 main.cpp
main.cpp: In function ‘int main(int, char**)’:
main.cpp:15:33: error: call of overloaded ‘f(main(int, char**)::<lambda(int)>)’ is ambiguous
main.cpp:15:33: note: candidates are:
main.cpp:6:6: note: void f(bool)
main.cpp:10:6: note: void f(std::function<double(int)>)
However if I replace the second function with a reference argument it compiles fine. And again if it is replaced by a const reference it fails.
So I have some questions concerning this example:
- Why is a lambda function implicitly convertible to
bool
in the first place?
- Why does taking a std::function reference solve the ambiguity?
- And most important to me, how can I avoid this problem? I need the second function to take either a (copy of a) std::function or a const reference to it.
回答1:
namespace details{
template<class Sig,class=void>
struct invoke {};
template<class F, class...Args>
struct invoke<F(Args...),decltype(void(
std::declval<F>()(std::declval<Args>()...)
))>{
using type=decltype(std::declval<F>()(std::declval<Args>()...));
};
}
template<class Sig>struct invoke:details::invoke<Sig>{};
template<typename Sig, typename T, typename=void>
struct invoke_test:std::false_type {};
template<typename R, typename...Args, typename T>
struct invoke_test<R(Args...), T,
typename std::enable_if<
std::is_convertible<
typename invoke<T(Args...)>::type,
R
>::value
>::type
>:std::true_type {};
template<typename...Args,typename T>
struct invoke_test<void(Args...),T,
decltype( void( typename invoke<T(Args...)>::type ) )
>:std::true_type{};
template<typename Sig, typename T>
constexpr bool invokable() {
return invoke_test<Sig,T>::value;
}
this gives us a pseudo-concept invokable
.
We can then use it like:
template<typename F>
typename std::enable_if<invokable<double(int),F>()>::type
f(F&&){
std::cout << "can be invoked\n";
}
void f(bool) {
std::cout << "is bool\n";
}
and bob is your uncle.
The real problem is that std::function<double(int)>
's constructor does not do a similar test, and instead claims (falsely) that it can be constructed from anything at all. This is a flaw in the standard, one I suspect will be fixed once concepts are standardized.
回答2:
A lambda function with no capture can be converted to a regular function pointer, which then has a standard conversion to a bool.
If you take the std::function
by non-const reference, then that eliminates it as a candidate, since converting the lambda to a std::function
requires a temporary, and a temporary cannot bind to a non-const reference. That just leaves f(bool)
as a candidate, so there is no ambiguity.
There are many ways you could avoid the ambiguity. For example, you could create a std::function
variable first:
std::function<double(int)> g = [](int _idx){ return 7.9;};
f(g);
or you could cast the lambda:
f(std::function<double(int)>([](int _idx){return 7.9;}));
You could have a helper function:
template<typename T>
std::function<T> make_function(T *f) { return {f}; }
int main ( int argc, char* argv[] ) {
f(make_function([](int _idx){ return 7.9;}));
return 0;
}
or you could grab the particular function you are interested in:
int main ( int argc, char* argv[] ) {
void (*f_func)(std::function<double(int)>) = f;
f_func([](int _idx){ return 7.9;});
return 0;
}
回答3:
You can get rid of the implicit conversion by creating a helper class
#include <functional>
#include <iostream>
struct Boolean {
bool state;
Boolean(bool b):state(b){}
operator bool(){ return state; }
};
void f(Boolean _switch) {
std::cout << "Nothing really " << _switch << std::endl;
}
void f(std::function<double (int)> _f) {
std::cout << "Nothing really, too" << std::endl;
}
int main ( int argc, char* argv[] ) {
f([](int _idx){ return 7.9;});
f(true);
return 0;
}
Should you ever want to call f
with eg. a pointer and expect it to call the first overload you will have to cast it to either bool
or add a corresponding constructor / cast to the helper class though.
回答4:
One more option to Vaughn Cato's answer:
template<typename F>
void f(F _f) {
std::cout << "Nothing really, too: " << _f(3) << std::endl;
}
Now the second overload is a template, so it is chosen for a lambda (or anything), and the first is chosen for bool
. So calling f
is no more complex than needed.
But, one problem with that is if you want to add more overloads, and another is that the first overload will only be called if there is an exact match to bool
.