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([&amp;](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&lt;double(int)&gt; 的构造函数没有做类似的测试,而是声称(错误地)它可以由任何东西构造。这是标准中的一个缺陷,我怀疑一旦概念标准化就会修复。

【讨论】:

【参考方案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&lt;T&gt;(T),您将获得unsignedchar、(有时)int16_t 或几乎任何通常会被良性转换为的模板intconst 转换和派生到基的转换也经常被程序员使用而没有考虑或意识到,这也会因像这样的重载集而中断。 @Adam 我说“如果你想要转化,当然不要那样做。”这包括您的 f(feof(some_FILE)) 示例。是的,在这种情况下这是一个非常糟糕的主意。 也许你应该通过使用一些 SFINAE 魔法来减少贪婪。 std::function 也应该这样做,尽管不是必须这样做。

以上是关于C++11 Lambda 函数隐式转换为 bool 与 std::function的主要内容,如果未能解决你的问题,请参考以下文章

windows编程问题 错误提示“int无法隐式转换为bool”

vs里面bool类型如何转换为string类型

公共 bool 方法不能将类型“bool”隐式转换为“void”

可以在 C++11 lambda 中隐式捕获参数包吗?

解决Html.CheckBoxFor中”无法将类型 bool 隐式转换为 bool。存在一个显式转换..."的方法

何时会发生隐式类型转换