C++11 Lambda 函数隐式转换为 bool 与 std::function
Posted
技术标签:
【中文标题】C++11 Lambda 函数隐式转换为 bool 与 std::function【英文标题】:C++11 Lambda functions implicit conversion to bool vs. std::function 【发布时间】:2022-01-23 23:23:05 【问题描述】:考虑这个简单的示例代码:
#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;
编译失败:
$ 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)>)
但是,如果我用引用参数替换第二个函数,它编译得很好。如果它被 const 引用替换,它再次失败。
所以我对这个例子有一些疑问:
为什么 lambda 函数首先可以隐式转换为bool
?
为什么采用 std::function 引用可以解决歧义?
对我来说最重要的是,如何避免这个问题?我需要第二个函数来获取(a 的副本)std::function 或对它的 const 引用。
【问题讨论】:
重载采用std::function
的函数目前不是一个好主意。在 C++11 中有一个 std::function
的贪婪构造函数模板,它不需要(SFINAE-)拒绝“无效”类型。也就是说,第二个重载可以匹配任何类型。不过,它随后会被列为用户定义的转化。
【参考方案1】:
可以将不带捕获的 lambda 函数转换为常规函数指针,然后将其标准转换为布尔值。
如果您通过非常量引用获取std::function
,则将其排除为候选对象,因为将 lambda 转换为 std::function
需要一个临时对象,而临时对象不能绑定到非常量引用。这样就只剩下f(bool)
作为候选人,所以没有歧义。
有很多方法可以避免歧义。例如,您可以先创建一个std::function
变量:
std::function<double(int)> g = [](int _idx) return 7.9;;
f(g);
或者你可以转换 lambda:
f(std::function<double(int)>([](int _idx)return 7.9;));
你可以有一个辅助函数:
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;
或者您可以获取您感兴趣的特定功能:
int main ( int argc, char* argv[] )
void (*f_func)(std::function<double(int)>) = f;
f_func([](int _idx) return 7.9;);
return 0;
【讨论】:
这可能是一个糟糕的解决方案(也可能不是),但使用f([&](int _idx) return 7.9;);
也应该可以,对吧?
不,因为没有在 lambda 函数中使用引用,编译器仍会将其转换为函数指针。只需使用,例如'argc',则不会转换为函数指针。
@bmm 从来没想过,谢谢。在 coliru 上尝试,g++ thinks it's ambiguous,但clang++ accepts it。
@bmm 该标准使这种转换运算符的存在取决于 lambda 是否具有 lambda-capture,而不是取决于它是否实际捕获任何内容。因此,cv_and_he 的解决方案应该起作用(虽然我会说这是一个 hack)。【参考方案2】:
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;
这给了我们一个伪概念invokable
。
然后我们可以像这样使用它:
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";
鲍勃是你的叔叔。
真正的问题是std::function<double(int)>
的构造函数没有做类似的测试,而是声称(错误地)它可以由任何东西构造。这是标准中的一个缺陷,我怀疑一旦概念标准化就会修复。
【讨论】:
【参考方案3】:您可以通过创建帮助类来摆脱隐式转换
#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;
如果你想用例如打电话给f
。一个指针并期望它调用第一个重载,您必须将其强制转换为 bool
或添加相应的构造函数/强制转换到辅助类。
【讨论】:
如果你也有相同函数的整数重载,这很遗憾失败,例如无效 f(int _i);当您使用 f(true) 调用它时,将选择整数重载,并将值强制为 1。:(【参考方案4】:Vaughn Cato's answer 的另一个选项:
template<typename F>
void f(F _f)
std::cout << "Nothing really, too: " << _f(3) << std::endl;
现在第二个重载是一个模板,所以它被选择用于 lambda(或任何东西),第一个被选择用于 bool
。所以调用f
并不比需要的复杂。
但是,这样做的一个问题是如果您想添加更多重载,另一个问题是只有在与 bool
完全匹配时才会调用第一个重载。
【讨论】:
我觉得重载非模板函数和模板函数通常是个坏主意,尤其是当模板名义上与非模板匹配时。如果您打算调用非模板,但您的类型不匹配完全,您最终将调用模板。例如,f(feof(some_FILE))
将调用模板,即使 feof()
仅在历史上偶然返回 int
(而不是 bool
)。
@Adam 取决于预期用途。如果你想要转换,你当然不会那样做。但是,如果您知道自己在做什么,那将是一个非常好的主意。我已经添加了限制,谢谢。
我从来没有发现这是一个好主意。 IMAO它太脆了。您是说在这种情况下,调用f(feof(some_FILE))
的人更愿意将int
视为一个函数?如果您有一些g(int)
重载了g<T>(T)
,您将获得unsigned
、char
、(有时)int16_t
或几乎任何通常会被良性转换为的模板int
。 const
转换和派生到基的转换也经常被程序员使用而没有考虑或意识到,这也会因像这样的重载集而中断。
@Adam 我说“如果你想要转化,当然不要那样做。”这包括您的 f(feof(some_FILE))
示例。是的,在这种情况下这是一个非常糟糕的主意。
也许你应该通过使用一些 SFINAE 魔法来减少贪婪。 std::function
也应该这样做,尽管不是必须这样做。以上是关于C++11 Lambda 函数隐式转换为 bool 与 std::function的主要内容,如果未能解决你的问题,请参考以下文章
windows编程问题 错误提示“int无法隐式转换为bool”
公共 bool 方法不能将类型“bool”隐式转换为“void”